js相关

79 阅读4分钟

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
    ...
    null640typeof 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
}

  1. 重写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
}
  1. 重写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
  }
  1. 重写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))
  }
}
  1. 函数柯里化
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
  1. 函数组合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));
  1. 类型检测
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
  }
  1. 12

  2. 12

  3. 12

  4. 12