前端工程师进阶之旅-手撕代码

主要包括一些工作中常用的方法,面试常问到的方法。还有一些不太了解,趁机深入了解的知识点。

废话少说,直接干代码就完事了。

数据类型判断

使用 Object.prototype.toString 实现。

function myTypeof(data) {
  return Object.prototype.toString.call(data).slice(8, -1).toLowerCase();
}

数组去重

//new Set() 集合
//ES6提供了新的数据结构Set。它类似于数组,但是成员的值都是唯一的,没有重复的值。
function unique(arr) {
  return [...new Set(arr)];
}

//数组去重 filter
function unique(arr) {
  return arr.filter((item, index, array) => {
    return array.indexOf(item) === index;
  });
}

// 数组去重  forEach
function unique(arr) {
  let res = [];
  arr.forEach((item) => {
    res.includes(item) ? "" : res.push(item);
  });
  return res;
}

数组排序

// 快排排序
function quickSort(arr) {
    // 数组长度为1 直接返回
    if (arr.length <= 1) return arr
    var left = [],
        right = []
    var centerIndex = Math.floor(arr.length / 2)
    // 定义中间值
    var center = arr[centerIndex];
    for (let i = 0; i < arr.length; i++) {
        // 小于中间值数据添加到left
        if (arr[i] < center) {
            left.push(arr[i])
        } else if (arr[i] > center) {
            // 大于中间值数据添加到right
            right.push(arr[i])
        }
    }
    // 递归返回 concat连接返回数据
    return quickSort(left).concat([center], quickSort(right))
}
// 冒泡排序
function bubbleSort(arr) {
    for (var i = 0; i < arr.length - 1; i++) {
        for (var j = i + 1; j < arr.length; j++) {
            if (arr[i] > arr[j]) {
                var temp = arr[j]
                arr[j] = arr[i]
                arr[i] = temp
            }
        }
    }
    return arr
}

数组扁平化

//使用 Infinity,可展开任意深度的嵌套数组
arr.flat(Infinity);

//适用JSON转换
JSON.parse("[" + JSON.stringify(arr).replace(/[|]/g, "") + "]");

//递归
function myFlat(arr) {
  let res = [];
  arr.forEach((item) => {
    if (Array.isArray(item)) {
      res = res.concat(myFlat(item));
    } else {
      res.push(item);
    }
  });
  return res;
}
//some
function myFlat(arr) {
  while (arr.some((res) => Array.isArray(res))) {
    arr = [].concat(...arr);
  }
  return arr;
}

深拷贝深克隆

// 简单克隆 无法复制函数
var newObj = JSON.parse(JSON.stringify(obj));

// 深克隆  无法克隆特殊实例  Date等
function deepClone(target) {
  if (typeof target !== "object") {
    return target;
  }
  var result;
  if (Object.prototype.toString.call(target) == "[object Array]") {
    // 数组
    result = [];
  } else {
    // 对象
    result = {};
  }
  for (var prop in target) {
    if (target.hasOwnProperty(prop)) {
      result[prop] = deepClone(target[prop]);
    }
  }
  return result;
}

//复杂版深克隆
function deepClone(target) {
  if (typeof target !== "object") return target;
  // 检测RegDate类型创建特殊实例
  let constructor = target.constructor;
  if (/^(RegExp|Date)$/i.test(constructor.name)) {
    return new constructor(target);
  }
  // 判断类型
  var result =
    Object.prototype.toString.call(target) == "[object Array]" ? [] : {};
  // 迭代循环
  for (var prop in target) {
    if (target.hasOwnProperty(prop)) {
      // 递归
      result[prop] = deepClone(target[prop]);
    }
  }
  return result;
}

继承方法

原型链继承:

// 原型链继承
// 问题:原型中的引用对象会被所有的实例共享,子类在实例化的时候不能给父类构造函数传参
function Father() {
  this.hobby = ["coding", "eat"];
}
Father.prototype.skill = function () {
  console.log("i will javascript");
};
function Son() {}
Son.prototype = new Father();

var father = new Father();
var son = new Son();
var son1 = new Son();
console.log(father.hobby); //[ 'coding', 'eat' ]

father.hobby.push("play");
console.log(father.hobby, son.hobby, son1.hobby);
//[ 'coding', 'eat', 'play' ] [ 'coding', 'eat' ] [ 'coding', 'eat' ]

son.hobby.push("hei");
console.log(father.hobby, son.hobby, son1.hobby);
//[ 'coding', 'eat', 'play' ] [ 'coding', 'eat', 'hei' ] [ 'coding', 'eat', 'hei' ]

son.skill(); //i will javascript

借用构造函数实现继承

// 原型链继承
// 问题:方法需要定义在构造函数内,因此每次创建子类实例都会创建一边方法
function Father(name) {
  this.name = name;
  this.sayNmae = function () {
    return this.name;
  };
}

function Son(name) {
  Father.call(this, name);
}
Son.prototype = new Father();

var father = new Father("wenbo");
var son = new Son("zhijian");

console.log(father.name, son.name); //wenbo zhijian
console.log(father.sayNmae(), son.sayNmae()); //wenbo zhijian

组合继承

//组合继承,结合原型链继承和借用构造函数,使用原型链继承原型上的属性和方法,借用构造函数继承实例属性。
//即可以把方法定义在原型上实现重用,又可以让每个实例都有自己的属性

// 组合继承
function Father(name) {
  this.name = name;
}
Father.prototype.sayName = function () {
  return this.name;
};

function Son(name, age) {
  Father.call(this, name);
  this.age = age;
}
Son.prototype = new Father();
Son.prototype.constructor = Son;

var son = new Son("yewen", 18);
console.log(son); //Son { name: 'yewen', age: 18 }
console.log(son.sayName()); //yewen

寄生式组合继承

//寄生组合继承
// 组合继承会导致调用两次父类构造函数
function Father(name) {
  this.name = name;
}
Father.prototype.sayName = function () {
  return this.name;
};

function Son(name, age) {
  Father.call(this, name);
  this.age = age;
}

Son.prototype = Object.create(Father.prototype);
Son.prototype.constructor = Son;

class 实现继承

// calss继承
class Father {
  constructor(name) {
    this.name = name;
  }
  getName() {
    return this.name;
  }
}

class Son extends Father {
  constructor(name, age) {
    super(name);
    this.age = age;
  }
  getAge() {
    return this.age;
  }
}
var son = new Son("heihei", 18);
console.log(son); //Son { name: 'heihei', age: 18 }
console.log(son.getName(), son.getAge()); //heihei 18

事件总线(发布订阅模式)

class EventEmitter {
  constructor() {
    this.cache = {};
  }
  //添加订阅
  on(name, fn) {
    if (this.cache[name]) {
      this.cache[name].push(fn);
    } else {
      this.cache[name] = [fn];
    }
  }
  //删除订阅
  off(name, fn) {
    let tasks = this.cache[name];
    if (tasks) {
      const index = tasks.findIndex((f) => f === fn || f.callback === fn);
      index >= 0 ? tasks.splice(index, 1) : "";
    }
  }
  //发布事件
  emit(name, once, ...args) {
    if (this.cache[name]) {
      // 创建副本
      let tasks = this.cache[name].slice();
      for (const fn of tasks) {
        fn(...args);
      }
      once ? delete this.cache[name] : "";
    }
  }
}

let demo = new EventEmitter();
demo.on("wenbo", function (data) {
  console.log("wenbo", data);
});
let fn1 = function (data) {
  console.log("hello:", data);
};
demo.on("wenbo", fn1);

demo.emit("wenbo", false, "world");
demo.off("wenbo", fn1);
demo.emit("wenbo", false, "world");

//wenbo world
//hello: world

//wenbo world

防抖函数

function debounce(fun, wait) {
  var timeId = null;
  return function () {
    var _this = this;
    var _arg = arguments;
    clearTimeout(timeId);
    timeId = setTimeout(function () {
      fun.apply(_this, _arg);
    }, wait);
  };
}

节流函数

function throttle(fun, wait) {
  var lastTime = 0;
  return function () {
    var _this = this;
    var _arg = arguments;
    var nowTime = new Date().getTime();
    if (nowTime - lastTime > wait) {
      fun.apply(_this, _arg);
      lastTime = nowTime;
    }
  };
}

图片加载优化懒加载

//   获取全部img元素 并且将类数组转化成数组
let imgList = [...document.querySelectorAll("img")];
let len = imgList.length;
// 图片懒加载
function imgLazyLoad() {
  let count = 0;
  return (function () {
    let isLoadList = [];
    imgList.forEach((item, index) => {
      let h = item.getBoundingClientRect();
      //  判断图片是否快要滚动道可视区域
      if (h.top < window.innerHeight + 200) {
        item.src = item.dataset.src;
        console.log(item.dataset.src);
        isLoadList.push(index);
        count++;
        // 全部加载 移出scroll事件
        if (len == count) {
          document.removeEventListener("scroll", imgLazyLoad);
        }
      }
    });
    // 移出已经加载完成的图片
    imgList = imgList.filter((img, index) => !isLoadList.includes(index));
  })();
}
// 节流函数
function throttle(fun, wait) {
  var lastTime = 0;
  return function () {
    var _this = this;
    var _arg = arguments;
    var nowTime = new Date().getTime();
    if (nowTime - lastTime > wait) {
      fun.apply(_this, _arg);
      lastTime = nowTime;
    }
  };
}
// 默认执行一次加载首屏图片
imgLazyLoad();
// 节流执行
document.addEventListener("scroll", throttle(imgLazyLoad, 200));

管理操作Cookie


var cookie = {
  //设置cookie
  set: function (name, value, time) {
    document.cookie = `${name}=${value};expires=${time};path=/`;
    return this;
  },
  //获取cookie
  get: function (name) {
        var arr;
        var reg = new RegExp('(^| )' + name + '=([^;]*)(;|$)');
        if ((arr = document.cookie.match(reg))) {
            return unescape(arr[2]);
        } else {
            return null;
        }
    },
   //移出token
   remove: function (name) {
    return this.setCookie(name, "", -1);
  },
};

封装 myForEach 方法

// thisValue 可选参数。当执行回调函数 callback 时,用作 this 的值。
Array.prototype.myForEach = function (callback, thisValue) {
    var _this
    // 当this为空抛出异常
    if (this == null) {
        throw new TypeError(' this is null or not defined');
    }
    //  var len = this.length 
    //  this.length >>> 0  相当于 所有非数值转换成0 ,所有大于等于 0 等数取整数部分
    var len = this.length >>> 0
    // callback不是函数时  抛出异常
    if (typeof callback !== "function") {
        throw new TypeError(callback + ' is not a function');
    }
    // 判断是够有传参 thisValue
    if (arguments.length > 1) {
        _this = thisValue
    }
    // 循环遍历
    for (var i = 0; i < len; i++) {
        // 回调函数  
        callback.call(_this, this[i], i, this)
    }
}

封装 myFilter 方法

Array.prototype.myFilter = function (callback, thisValue) {
    var _this
    var arr = []

    if (this == null) {
        throw new TypeError(' this is null or not defined');
    }

    var len = this.length >>> 0

    if (typeof callback !== "function") {
        throw new TypeError(callback + ' is not a function');
    }

    if (arguments.length > 1) {
        _this = thisValue
    }

    for (var i = 0; i < len; i++) {
        callback.call(_this, this[i], i, this) && arr.push(this[i])
    }
    return arr
}

封装 myMap 方法

Array.prototype.myMAp = function (callback, thisValue) {
    var _this
    var arr = []

    if (this == null) {
        throw new TypeError(' this is null or not defined');
    }

    var len = this.length >>> 0

    if (typeof callback !== "function") {
        throw new TypeError(callback + ' is not a function');
    }
    if (arguments.length > 1) {
        _this = thisValue
    }

    for (var i = 0; i < len; i++) {
        arr.push(callback.call(_this, this[i], i, this))
    }
    return arr
}

封装 myEvery 方法

Array.prototype.myEvery = function (callback, thisValue) {
    var _this

    if (this == null) {
        throw new TypeError(' this is null or not defined');
    }

    var len = this.length >>> 0

    if (typeof callback !== "function") {
        throw new TypeError(callback + ' is not a function');
    }

    if (arguments.length > 1) {
        _this = thisValue
    }

    for (var i = 0; i < len; i++) {
        if (!callback.call(_this, this[i], i, this)) {
            return false
        }
    }
    return true
}

封装 myReduce 方法

Array.prototype.myEvery = function (callback, initialValue) {
    var value = 0
    console.log(value)
    if (this == null) {
        throw new TypeError(' this is null or not defined');
    }
    var len = this.length >>> 0
    if (typeof callback !== "function") {
        throw new TypeError(callback + ' is not a function');
    }

    if (arguments.length > 1) {
        value = initialValue
    }
    for (var i = 0; i < len; i++) {
        value = callback(value, this[i], i, this)
    }
    return value
}

获取 URL 参数返回对象

function getURLParam(url) {
  let obj = {};
  url.replace(/(?<=[?|&])(w+)=(w+)/g, function (data, key, value) {
    if (obj[key]) {
      obj[key] = [].concat(obj[key], value);
    } else {
      obj[key] = value;
    }
  });
  return obj;
}

HTML 字符串模板

function render(template, data) {
  return template.replace(/{(w+)}/g, function ($1, key) {
    return data[key] ? data[key] : "";
  });
}

let html = "我叫{name},今年{id}岁。";
let data = {
  name: "Yevin",
  age: 18,
};
render(html, data); //我叫Yevin,今年18岁

利用 JSONP 实现跨域请求

function jsonp(url, callbackName) {
  return new Promise((resolve, reject) => {
    var script = document.createElement("script");
    script.src = "demo.js";
    document.body.appendChild(script);

    window[callbackName] = function (res) {
      //移除remove
      script.remove();
      //返回数据
      resolve(res);
    };
  });
}

原生 JS 封装 AJAX

function Ajax(method, url, callback, data, async = true) {
  var xhr;
  //同一转换method方法为大写
  method = method.toUpperCase();
  // 开启XMLHTTPRequest
  xhr = new XMLHttpRequest();
  // 监控状态变化 执行回调函数
  xhr.onreadystatechange = function () {
    if (xhr.readyState == 4 && xhr.readyState == 200) {
      // 回调函数返回参数
      callback(xhr.responseText);
    }
  };
  // 判断请求方式 get post或者其他
  if (method == "GET") {
    xhr.open("GET", url, async);
  } else if (method == "POST") {
    xhr.open("POST", url, async);
    // 设置请求头
    xhr.setRequestHeader("Content-Type", "application/json");
    // 发送参数
    xhr.send(data);
  }
}

函数柯里化

//把多个参数的函数变换成接受一个单一参数的函数,并且返回接受余下的参数且返回结果的新函数的技术

function curry(fun) {
  let fn = function (...arg) {
    if (arg.length == fun.length) return fun(...arg);
    return (...arg2) => fn(...arg, ...arg2);
  };
  return fn;
}

function demo(a, b) {
  return a * b;
}
console.log(demo(1, 2)); //2
console.log(curry(demo)(1)(2)); //2

偏函数

// 偏函数,就是固定一个函数的一个或者多个参数,返回一个新的函数,这个函数用于接受剩余的参数。
function partial(fn, ...arg) {
  return function (...args) {
    return fn(...arg, ...args);
  };
}
function demo(a, b, c) {
  console.log(a, b, c); // 1, 2,3
}
var a = partial(demo, 1);
a(2, 3);
内容来源于网络如有侵权请私信删除