1. JS中有关小数(浮点数)的计算会出现精准度丢失的问题
-
JS中所有值都是以二进制在计算机底层进行存储的{浮点数转为二进制,可能出现无线循环的情况}
-
在计算机底层存储的时候,最多存储64位【舍弃了一些值,值本身就失去了精准度】
浮点数计算的解决方案
-
tofixed保留小数点后面N位,他自己会四舍五入
-
扩大系数法
const coefficient = function coefficient(num) {
num = num + '';
let [, char = ''] = num.split('.'),
len = char.length;
return Math.pow(10, len); //-> 10**len
};
const plus = function plus(num1, num2) {
num1 = +num1;
num2 = +num2;
if (isNaN(num1) || isNaN(num2)) return NaN;
let max = Math.max(coefficient(num1), coefficient(num2));
return (num1 * max + num2 * max) / max;
};
console.log(plus(0.1, 0.2));
2. 数据类型检测
平时项目中会涉及数据类型的检测
- typeof
- instanceof
- constructor
- Object.prototype.toString.call
- Array.isArray()
- isNaN
- ...
typeof 检测数据类型
-
所有数据类型值,在计算机底层都是按照“64位”的二进制进行存储的
-
typeof 是按照二进制值进行检测类型的
二进制的前三位是0,认为是对象,然后再去看有没有实现call方法,如果实现call方法,返回 function ,没有实现则返回 object ... null 是64个 0, typeof null -> object 【局限性】 -
检测未被声明的变量,值是'undefined'
BigInt大数类型
- Number.MAX_SAFE_INTEGER
- Number.MIN_SAFE_INTEGER
超过安全数后,进行运算或者访问,结果会不准确
解决方案:
- 服务器返回给客户端的大数,按照“字符串”格式返回
- 客户端吧其变成 BigInt,然后按照 BigInt进行运算
- 最后把运算后的 BigInt转换为字符串,在传递给服务器即可
// console.log(BigInt('90071992547409912434234') + BigInt(12345));
// console.log((90071992547409912446579n).toString());
Symbol 创建一个唯一值
- 给对象设置“唯一值”属性名
- 字符串
- Symbol类型
- Map 新的数据结构: 可以允许属性名是对象
- Symbol.asyncIterator/iterator/hansInstance/toPrimitive/toStringTag...
- 在派发行为标识统一进行管理的时候,可以基于 Symbol 类型的值,保证微微标识的唯一性
3. 手撕
1.防抖
const debounce = function debounce(func, wait, immediate) {
// init params
if (typeof func !== "function") throw new TypeError("func is not a function!");
if (typeof wait === "boolean") {
immediate = wait;
wait = undefined;
}
wait = +wait;
if (isNaN(wait)) wait = 300;
if (typeof immediate !== "boolean") immediate = false;
// handler
let timer = null;
return function operate(...params) {
// now:记录是否是立即执行「第一次点击&immediate=true」
let now = !timer && immediate;
// 清除之前设置的定时器
timer = clearTimer(timer);
timer = setTimeout(() => {
// 结束边界触发
if (!immediate) func.call(this, ...params);
// 清除最后一个定时器
timer = clearTimer(timer);
}, wait);
// 如果是立即执行,则第一次执行operate就把要干的事情做了即可 “开始边界触发”
if (now) func.call(this, ...params);
};
};
2.节流
const throttle = function throttle(func, wait) {
// init params
if (typeof func !== "function") throw new TypeError("func is not a function!");
wait = +wait;
if (isNaN(wait)) wait = 300;
// handler
let timer = null,
previous = 0; //记录上一次func触发的时间
return function operate(...params) {
let now = +new Date(),
remaining = wait - (now - previous);
if (remaining <= 0) {
// 两次触发的间隔时间超过设定的频率,则立即执行函数
func.call(this, ...params);
previous = +new Date();
timer = clearTimer(timer);
} else if (!timer) {
// 间隔时间不足设定的频率,而且还未设置等待的定时器,则设置定时器等待执行函数即可
timer = setTimeout(() => {
func.call(this, ...params);
previous = +new Date();
timer = clearTimer(timer);
}, remaining);
}
};
};
3. 重写instanceof
function myInstanceof(obj,ctor) {
if (typeof ctor !== 'function') throw TypeError('stor is not a func')
if (obj === null || !/(function|object)/.test(typeof obj)) throw TypeError('obj is not a object')
let proto = Object.getPrototypeOf(obj)
while (proto) {
if (proto === ctor.prototype) return true
proto = Object.getPrototypeOf(proto)
}
return false
}
- 重写new
/* 重写new操作符 */
function my_new(Con, ...args) {
// 创建一个新的对象
let A = {};
// 往空对象挂载构造函数Con 的原型对象
Object.setPrototypeOf(A, Con.prototype)
// 执行构造函数:改变构造函数this指向,指向空对象A,往A中注入属性
let B = Con.call(this, ...args);
// 判断构造函数是否返回对象,是则去返回值,否则取最初创建的对象A
const newObj = B instanceof Object ? B : A
return newObj
}
- 重写call
Function.prototype.call = function call(context,...params) {
// this -> fn context-> obj, params -> [10,20]
// 两个 == 包括undefined / null
if (context == null) context = window
if(!/^(function| object)$/.test(typeof context)) Object(context)
// 思路: 给 context 设置一个属性 [例如: xxx 新增的属性不要和原始 context 中的属性冲突(设置symbol唯一值属性)],
// 让属性值等于要执行的函数(既: this [fn] );后面就可以基于 context.xxx() 执行,这样既把函数执行了,
// 也让函数中的 this 改为 context 了
let self = this,
key = Symbol('KEY'),
result;
context[key] = self;
result = context[key](...params)
// 新增属性之后,函数执行结果之后,需要把新增的属性删除
Reflect.deleteProperty(context, key);// ES6中可以基于 Reflect.deleteProperty(对象,键名)
// delete context[key]; // 新增的属性用完后记得移除,可以兼容性
return result
}
- 重写bind
submit.onclick = fn.bind(obj, 10, 20); // 这样处理就可以了
Function.prototype.bind = function bind(context, ...params) {
// this -> fn, context-> obj, params -> [10,20]
let self = this;
return function operate(...args) {
// args -> [ev],点击事件,默认传了一个ev 事件对象
// this -> submit
self.call(context,...params.concat(args))
}
}
- 函数柯里化
const curring = function curring() {
let params = [];
const add = (...args) => {
params = params.concat(args);
return add;
};
add[Symbol.toPrimitive] = () => params.reduce((result, item) => result + item);
return add;
};
let add = curring();
let res = add(1)(2)(3);
console.log(res); //->6
add = curring();
res = add(1, 2, 3)(4);
console.log(res); //->10
add = curring();
res = add(1)(2)(3)(4)(5);
console.log(res); //->15
/* 函数科柯里化 */
function currying(fn, length) {
// 第一次调用获取函数 fn 参数的长度,后续调用获取 fn 剩余参数的长度
length = length || fn.length;
// currying 包裹之后返回一个新函数,接收参数为 ...args
return function (...args) {
// 新函数接收的参数长度是否大于等于 fn 剩余参数需要接收的长度
return args.length >= length
? fn.apply(this, args) // 满足要求,执行 fn 函数,传入新函数的参数
: currying(fn.bind(this, ...args), length - args.length)
// 不满足要求,递归 currying 函数,新的 fn 为 bind 返回的新函数
//(bind 绑定了 ...args 参数,未执行),新的 length 为 fn 剩余参数的长度
}
}
const sum = function(a,b,c){
return a + b + c;
}
const currySum = currying(sum);
console.log(currySum(1,2,3));
console.log(currySum(1)(2)(3));
console.log(currySum(1,2)(3));
// 输出都是 6
- 函数组合compose
/* 函数组合 */
const add1 = x => x + 1;
const mul3 = x => x * 3;
const div2 = x => x / 2;
const compose = function compose(...funcs) {
// init params
let len = funcs.length;
if (len == 0) return x => x
if (len == 1) return funcs[0]
return function operate(x) {
return funcs.reduceRight((result, func) => {
return func(result)
},x)
}
}
const operate = compose(div2, mul3, add1, add1);
console.log(operate(0));
- 类型检测
const toType = function toType(obj) {
const reg = /^\[object ([\w\W]+)\]$/;
if (obj === null) return obj + "";
// const isObj = typeof obj === 'object' || typeof obj === 'function';
const isObj = /^(object|fuction)$/.test(typeof obj),
getObjClass = Object.prototype.toString;
// reg.exec(getObjClass.call(obj)) 返回的是一个数组,数组第二项[1]表示 第一个括号内的匹配结果
return isObj ? reg.exec(getObjClass.call(obj))[1].toLowerCase(): typeof obj
}
-
12
-
12
-
12
-
12