js中字符串,json数据的处理【匹配url、邮箱、电话,版本号,千位分割,判断回问】-CSDN博客
手写实现js类/方法【改变this、map、Object、发布订阅、promise】-CSDN博客
目录
A.JSON.parse() 和 JSON.stringify()
arr instanceof Array ? [] : {}
Symbol属性:Object.getOwnPropertySymbols
requestAnimationFrame代替settimeout更精准
setTimeout、setInterval最短时长为4ms
arr.filter((value, index, self) => self.indexOf(value) === index)
new :Fn=[...arguments].shift()
*寄生组合式继承:call,create,constructor
Object.defineProperty(obj, prop, descriptor)
防抖debounce
被打断的时间+原定time执行一次
并且执行的是最后一次触发的事件
触发事件后在 n 秒内函数至多只能执行一次,如果在 n 秒内又触发了事件,会重计算函数执行时间,延迟执行
本该在-----执行
但是--再次触发
于是-------执行
必须等待完time,即才能执行,time期间触发都会从0计时
function debounce(fun,time) {
let flag // 定义状态
return function () {
clearTimeout(flag)// 在执行之前 清除 定时器的 flag 不让他执行
flag = setTimeout(() => {
fun.call(this,arguments)//拿到正确的this对象,即事件发生的dom
}, time)
}
}
应用
search搜索联想
用户在不断输入值时,用防抖来节约请求资源。
window触发resize
不断的调整浏览器窗口大小会不断的触发这个事件,用防抖来让其只触发一次
节流throttle
间隔至少time执行
无视打断,time内执行的是第一次触发的事件
连续触发事件但是在 n 秒中只执行一次函数。
定时在-----执行
在--再次触发,无效
最终在-----执行
两种方式可以实现,分别是时间戳版和定时器版。
function throttle(fun, time) {
let flag // 定义一个空状态
return function () { // 内部函数访问外部函数形成闭包
if (!flag) { // 状态为空执行
flag = setTimeout(() => {
fns.apply(this, arguments) // 改变this指向 把 event 事件对象传出去
flag = null // 状态为空
}, time)
}
}
}
应用
鼠标不断点击触发
mousedown(单位时间内只触发一次)
监听滚动事件
比如是否滑到底部自动加载更多,用throttle来判断
深拷贝
A.JSON.parse() 和 JSON.stringify()
- 数据类型限制:不能复制、undefined、Date、Symbol、函数等等其他的js数据类型,其实也很好理解,因为JSON只支持以上列举的数据类型,其他要么被忽略要么报错。
- 丢失原型的信息:最终拷贝的对象原型只会是
Object.prototype
。- 不能处理循环引用:对象包含循环引用,JSON.stringify() 将会导致错误。
- 性能问题:对于大型复杂对象或深度嵌套的对象,容易产生性能问题
const originalObject = { a: 1, b: { c: 2 } };
// 通过将对象转换为字符串,再将其解析回对象来实现深拷贝
const deepCopy = JSON.parse(JSON.stringify(originalObject));
console.log(deepCopy); // 输出深拷贝后的对象
B.遍历+递归
arr instanceof Array ? [] : {}
function deepClone(obj) {
if (typeof obj !== "object" || obj === null) {
return obj;
}
const res = Array.isArray(obj) ? [] : {};
for (const key in obj) {
if (obj.hasOwnProperty(key)) {
res[key] = deepClone(obj[key]);
}
}
return res;
}
要拷贝的属性只能是对象自身的,不能是原型链上的
拷贝的属性不仅仅是
string
类型的键,还有symbol
类型的键对象身上可能会有循环引用,需要处理,而不是陷入死循环
C.改进版
Symbol属性:Object.getOwnPropertySymbols
Symbols 在 for...in 迭代中不可枚举。
Object.getOwnPropertyNames() 不会返回 symbol 对象的属性
// 复制 Symbol 类型属性
const symbols = Object.getOwnPropertySymbols(obj);
for (const symbolKey of symbols) {
res[symbolKey] = deepClone(obj[symbolKey]);
}
不可枚举属性:Reflect.ownKeys
Reflect.ownKeys
简单来说会返回一个数组,属性包括:对象自身的所有属性(包括所有可枚举的和不可枚举的的string和symbol类型
// 拿到对象身上所有的属性,返回一个数组
const keys = Reflect.ownKeys(obj);
for (const key of keys) {
res[key] = deepClone(obj[key]);
}
循环引用:WeakMap
在每次对复杂数据类型进行深拷贝前保存其值,如果下次又出现了该值,就不再进行拷贝,直接截止。
WeakMap
中的键是弱引用,当键对象在其他地方没有被引用时,它可以被垃圾回收,这有助于防止内存泄漏。
function deepClone(obj, clones = new WeakMap()) {
// 如果是原始类型或 null,直接返回
if (typeof obj !== "object" || obj === null) {
return obj;
}
// 检查是否已经克隆过该对象,防止循环引用
if (clones.has(obj)) {
return clones.get(obj);
}
// 判断是否数组还是普通对象
const res = Array.isArray(obj) ? [] : {};
// 将当前对象添加到克隆Map中
clones.set(obj, res);
// 拿到所有 key
const keys = Reflect.ownKeys(obj);
for (const key of keys) {
res[key] = deepClone(obj[key], clones);
}
return res;
}
// test
const obj = {
foo: "bar",
num: 42,
arr: [1, 2, 3],
obj: { dd: true },
[Symbol("symbol属性")]: "hello",
};
// 循环引用
obj.newObj = obj;
const copy = deepClone(obj);
console.log(copy);
setTimeout()
倒计时
//返回值timeoutID是一个正整数,表示定时器的编号。
let timeoutID = scope.setTimeout(function[, delay]),//delay表示最小等待时间,真正等待时间取决于前面排队的消息
clearTimeout(timeoutID) //取消该定时器。
js:递归调用
var c = 10; // 设置初始倒计时时间为10秒
var t; // 声明一个变量用来存储 setTimeout 的返回值
function timedCount() {
c -= 1; // 每次调用函数,倒计时时间减一秒
if (c === 0) {
clearTimeout(t); // 当倒计时为零时,清除定时器
console.log("倒计时结束"); // 可以在这里添加任何你想要执行的操作,比如提示用户倒计时结束
return;
}
console.log("剩余时间:" + c + "秒"); // 在控制台打印剩余时间,你也可以将其显示在页面上的某个元素中
t = setTimeout(function () {
timedCount();
}, 1000); // 每隔一秒调用一次自身,实现倒计时
}
timedCount(); // 调用函数开始倒计时
react:useEffect(()=>{},[cnt])
import React, { useState, useEffect } from 'react';
const Countdown = ({ initialCount }) => {
const [count, setCount] = useState(initialCount);
useEffect(() => {
// 如果倒计时已经结束,则不再减少计时
if (count === 0) return;
// 设置定时器
const timerId = setTimeout(() => {
setCount(count - 1);
}, 1000);
// 清理函数
return () => clearTimeout(timerId);
}, [count]);
return (
<div>
<h1>倒计时: {count} 秒</h1>
</div>
);
};
export default Countdown;
requestAnimationFrame代替settimeout更精准
startTime = Date.now();
requestAnimationFrame(function update() {
var currentTime = Date.now();
var deltaTime = currentTime - startTime;
if (deltaTime >= 1000) { // 每秒更新一次
startTime = currentTime;
timedCount(); // 递归调用自身,实现倒计时
} else {
requestAnimationFrame(update); // 继续请求下一帧
}
});
setTimeout、setInterval最短时长为4ms
setTimeout固定时长后执行
setInterval间隔固定时间重复执行
setTimeout模拟实现setInterval
// 使用闭包实现
function mySetInterval(fn, t) {
let timer = null;
function interval() {
fn();
timer = setTimeout(interval, t);
}
interval();
return {
// cancel用来清除定时器
cancel() {
clearTimeout(timer);
}
};
}
setInterval模拟实现setTimeout
function mySetTimeout(fn, time) {
let timer = setInterval(() => {
clearInterval(timer);
fn();
}, time);
}
// 使用
mySetTimeout(() => {
console.log(1);
}, 2000);
数组去重
[...new Set(arr)]
arr.filter((value, index, self) => self.indexOf(value) === index)
浮点数不精确性
原因:部分十进制小数在二进制中无限循环
Java、Python、C++、JS都是基于 IEEE 754 标准的双精度浮点数来表示数字
0.2的二进制表示是无限循环的,近似等于0.0011001100110011001100
IEEE-754 标准下双精度浮点数由三部分组成,分别如下:
- sign(符号): 占 1 bit, 表示正负;
- exponent(指数): 占 11 bit,表示范围;
- mantissa(尾数): 占 52 bit,表示精度,多出的末尾如果是 1 需要进位;
阅读 JavaScript 浮点数陷阱及解法可以了解到公式的由来。
解决
面试:toFixed+精度
/*** method **
* add / subtract / multiply /divide
* floatObj.add(0.1, 0.2) >> 0.3
* floatObj.multiply(19.9, 100) >> 1990
*
*/
var floatObj = function() {
/*
* 判断obj是否为一个整数
*/
function isInteger(obj) {
return Math.floor(obj) === obj
}
/*
* 将一个浮点数转成整数,返回整数和倍数。如 3.14 >> 314,倍数是 100
* @param floatNum {number} 小数
* @return {object}
* {times:100, num: 314}
*/
function toInteger(floatNum) {
var ret = {times: 1, num: 0}
if (isInteger(floatNum)) {
ret.num = floatNum
return ret
}
var strfi = floatNum + ''
var dotPos = strfi.indexOf('.')
var len = strfi.substr(dotPos+1).length
var times = Math.pow(10, len)
var intNum = Number(floatNum.toString().replace('.',''))
ret.times = times
ret.num = intNum
return ret
}
/*
* 核心方法,实现加减乘除运算,确保不丢失精度
* 思路:把小数放大为整数(乘),进行算术运算,再缩小为小数(除)
*
* @param a {number} 运算数1
* @param b {number} 运算数2
* @param digits {number} 精度,保留的小数点数,比如 2, 即保留为两位小数
* @param op {string} 运算类型,有加减乘除(add/subtract/multiply/divide)
*
*/
function operation(a, b, digits, op) {
var o1 = toInteger(a)
var o2 = toInteger(b)
var n1 = o1.num
var n2 = o2.num
var t1 = o1.times
var t2 = o2.times
var max = t1 > t2 ? t1 : t2
var result = null
switch (op) {
case 'add':
if (t1 === t2) { // 两个小数位数相同
result = n1 + n2
} else if (t1 > t2) { // o1 小数位 大于 o2
result = n1 + n2 * (t1 / t2)
} else { // o1 小数位 小于 o2
result = n1 * (t2 / t1) + n2
}
return result / max
case 'subtract':
if (t1 === t2) {
result = n1 - n2
} else if (t1 > t2) {
result = n1 - n2 * (t1 / t2)
} else {
result = n1 * (t2 / t1) - n2
}
return result / max
case 'multiply':
result = (n1 * n2) / (t1 * t2)
return result
case 'divide':
result = (n1 / n2) * (t2 / t1)
return result
}
}
// 加减乘除的四个接口
function add(a, b, digits) {
return operation(a, b, digits, 'add')
}
function subtract(a, b, digits) {
return operation(a, b, digits, 'subtract')
}
function multiply(a, b, digits) {
return operation(a, b, digits, 'multiply')
}
function divide(a, b, digits) {
return operation(a, b, digits, 'divide')
}
// exports
return {
add: add,
subtract: subtract,
multiply: multiply,
divide: divide
}
}();
业务:第三方库
bignumber.js:
处理大数字和高精度运算
Object
new :Fn=[...arguments].shift()
"_new"函数,该函数会返回一个对象,
该对象的构造函数为函数参数、原型对象为函数参数的原型,核心步骤有:
- 创建一个新对象
- 获取函数参数
- 将新对象的原型对象和函数参数的原型连接起来
- 将新对象和参数传给构造器执行
- 如果构造器返回的不是对象,那么就返回第一个新对象
const _new = function() {
const object1 = {}
const Fn = [...arguments].shift()
object1.__proto__ = Fn.prototype
const object2 = Fn.apply(object1, arguments)
return object2 instanceof Object ? object2 : object1
}
*寄生组合式继承:call,create,constructor
通过寄生组合式继承使"Chinese"构造函数继承于"Human"构造函数。要求如下:
1. 给"Human"构造函数的原型上添加"getName"函数,该函数返回调用该函数对象的"name"属性
2. 给"Chinese"构造函数的原型上添加"getAge"函数,该函数返回调用该函数对象的"age"属性
- 在"Human"构造函数的原型上添加"getName"函数
- 在”Chinese“构造函数中通过call函数借助”Human“的构造器来获得通用属性
- Object.create函数返回一个对象,该对象的__proto__属性为对象参数的原型。此时将”Chinese“构造函数的原型和通过Object.create返回的实例对象联系起来
- 最后修复"Chinese"构造函数的原型链,即自身的"constructor"属性需要指向自身
- 在”Chinese“构造函数的原型上添加”getAge“函数
function Human(name) {
this.name = name
this.kingdom = 'animal'
this.color = ['yellow', 'white', 'brown', 'black']
}
Human.prototype.getName = function() {
return this.name
}
function Chinese(name,age) {
Human.call(this,name)//call函数借助”Human“的构造器来获得通用属性
this.age = age
this.color = 'yellow'
}
//返回的对象__proto__属性为对象参数的原型
Chinese.prototype = Object.create(Human.prototype)//使用现有的对象来作为新创建对象的原型
//修复"Chinese"构造函数的原型链,即自身的"constructor"属性需要指向自身
Chinese.prototype.constructor = Chinese
Chinese.prototype.getAge = function() {
return this.age
}