JS手写

117 阅读4分钟

深拷贝

/* 1. JSON
会丢失原型链;
不支持值为 undefined、函数;
不支持循环引用;
 */
const deepCloneObj = JSON.parse(JSON.stringify(obj));

/* 2. 递归
 */

function deepClone(obj, cache = new WeakMap()) {
  if (obj === null || typeof obj !== "object") return obj;
  if (obj instanceof Date) return new Date(obj);
  if (obj instanceof RegExp) return new RegExp(obj);

  // 如果出现循环引用,返回缓存的对象,防止递归死循环
  if (cache.has(obj)) return cache.get(obj);

  // 使用被 copy 对象的构造函数来创建一个新对象; 并缓存
  let cloneObj = new obj.constructor();
  cache.set(obj, cloneObj);

  // 遍历 & 递归拷贝每个 key
  for (let key in obj) {
    if (obj.hasOwnProperty(key)) {
      // 递归
      cloneObj[key] = deepClone(obj[key], cache);
    }
  }

  return cloneObj;
}

// test
const obj = { name: "Jack", address: { x: 100, y: 200 } };
obj.a = obj; // 循环引用
const newObj = deepClone(obj);
console.log(newObj.address === obj.address); // false

判断是否是一个数组

// 1.
const arr = [];

console.log(
  Array.isArray([]),
  Array.isArray(arr),
  Array.isArray([1]),
  Array.isArray(new Array()),
  Array.isArray(new Array(1, 2, 3, 4, 5)),
  Array.isArray(Array.prototype)
); // true

// 2. constructor
// 每个对象都有 constructor,指向创建当前对象的构造函数
// 但是注意此属性可能被修改
console.log(arr.constructor === Array); // true

// 3. instanceof Array
// 检测某个对象的原型链中是否出现 Array.prototype
// 但要注意,构造函数的 prototype 和 对象的原型链都有可能改变;
// 在有 iframe 的页面脚本中使用 instanceof,可能会得到错误的结果,因为 iframe 拥有独立的全局环境,不同的全局环境拥有不同的全局对象,从而拥有不同的内置类型构造函数。(用 1 判断更准确)
console.log(
  Array.prototype instanceof Object,
  [] instanceof Array,
  arr instanceof Array
); // true

// 4. isPrototypeOf
// 测试某个对象是否在传入参数的原型链上
console.log(Array.prototype.isPrototypeOf(arr)); // true

// 5. Object.prototype.toString
// 如果此方法在自定义对象中未被覆盖,toString() 返回 "[object type]" 字符串,其中 type 是对象的类型。
// 需要以绑定 this 的形式来调用
console.log(
  Object.prototype.toString.call(arr),
  Object.prototype.toString.call(arr) === "[object Array]"
);

reduce

reduce 实现 map

Array.prototype.myMap = function (callback) {
  if (typeof callback !== "function")
    throw new Error("callback must be a function!");

  return this.reduce((pre, val, ind, arr) => {
    pre.push(callback(val, ind, arr));
    return pre;
  }, []);
};

const nums = [1, 5, 6].myMap((v) => v + 1);
console.log(nums);

reduce 实现 filter

Array.prototype.myFilter = function (callback) {
  if (typeof callback !== "function")
    throw new Error("callback must be a function!");

  return this.reduce((pre, val, ind, arr) => {
    if (callback(val, ind, arr)) pre.push(val);

    return pre;
  }, []);
};

const nums2 = [1, 5, 6].myFilter((v) => v > 2);
console.log(nums2);

reduce 实现数组去重

let arr = [1, 2, 3, 1, 1, 2, 3, 3, 4, 3, 4, 5];

const res = arr.reduce((pre, val, ind, arr) => {
  if (!pre.includes(val)) pre.push(val);

  return pre;
}, []);

console.log(res);

reduce 实现数组扁平化

let arr2 = [
  1,
  2,
  "3js",
  [4, 5, [6], [7, 8, [9, 10, 11], null, "abc"], { age: 58 }, [13, 14]],
  "[]",
  null,
];

function myFlat(arr) {
  if (!Array.isArray(arr)) throw new Error(`${arr} must be array`);

  return arr.reduce((pre, val, ind, arr) => {
    return Array.isArray(val) ? [...pre, ...myFlat(val)] : [...pre, val];
  }, []);
}

console.log(myFlat(arr2));

防抖

/*
防抖:指定事件触发后 n 秒,执行回调,如果在这期间再次触发,则重新计时;
简单地说,就是当一个函数连续触发,只会执行最后一次

应用场景:
1、搜索框搜索输入,用户最后一次输入完,再发送请求;
2、用户名、手机号、邮箱输入验证;
3、浏览器窗口大小改变后,只需窗口调整完后,再执行resize事件中的代码,防止重复渲染。
 */

// 1. 定时器实现
//  fn是要调用的函数,delay是防抖的时间
function debounce(fn, delay) {
  let timer = null;

  // 返回一个闭包函数,用闭包保存timer确保其不会销毁,重复点击会清理上一次的定时器
  return () => {
    // 保存事件参数,防止fn函数需要事件参数里的数据
    let args = arguments;

    // 调用一次就清除上一次的定时器
    clearTimeout(timer);

    // 开启这一次的定时器
    timer = setTimeout(() => {
      // 若不改变this指向,则会指向fn定义环境
      fn.apply(this, args);
    }, delay);
  };
}

节流

/* 函数节流(throttle):规定在一个单位时间内,只能触发一次函数。如果这个单位时间内触发多次函数,只有一次生效。

应用场景:
1、鼠标不断点击触发,mousedown 在单位时间内只触发一次;
2、监听滚动事件,比如是否滑倒底部自动加载更多;
3、表单验证;
4、按钮提交事件。
 */

// 方法一:时间戳
function throttle(fn, delay = 1000) {
  // 记录第一次的调用时间
  var prev = null;
  console.log(prev);
  // 返回闭包函数
  return function () {
    // 保存事件参数
    var args = arguments;
    // 记录现在调用的时间
    var now = Date.now();
    // console.log(now);
    // 如果间隔时间大于等于设置的节流时间
    if (now - prev >= delay) {
      // 执行函数
      fn.apply(this, args);
      // 将现在的时间设置为上一次执行时间
      prev = now;
    }
  };
}

JS 判断当前运行环境是手机还是电脑

var sUserAgent = navigator.userAgent.toLowerCase();
var bIsIpad = sUserAgent.match(/ipad/i) == "ipad";
var bIsIphoneOs = sUserAgent.match(/iphone os/i) == "iphone os";
var bIsMidp = sUserAgent.match(/midp/i) == "midp";
var bIsUc7 = sUserAgent.match(/rv:1.2.3.4/i) == "rv:1.2.3.4";
var bIsUc = sUserAgent.match(/ucweb/i) == "ucweb";
var bIsAndroid = sUserAgent.match(/android/i) == "android";
var bIsCE = sUserAgent.match(/windows ce/i) == "windows ce";
var bIsWM = sUserAgent.match(/windows mobile/i) == "windows mobile";
if(bIsIpad || bIsIphoneOs || bIsMidp || bIsUc7 || bIsUc || bIsAndroid || bIsCE || bIsWM) {
alert("您是手机登录");
} else {
alert("您是电脑登录");
}