Jason Lee
首页
  • 学习笔记

    • 常用资源
    • html&css
    • webpack
    • vue2.x
    • vue3
    • typescript入门
    • 工程化实践
  • css
  • javascript
  • es6常问
  • 手写代码
  • 错误监控
  • webpack
  • vue框架
  • 浏览器相关
  • 计算机网络
  • 数据结构和算法
  • 学习笔记
工具
首页
  • 学习笔记

    • 常用资源
    • html&css
    • webpack
    • vue2.x
    • vue3
    • typescript入门
    • 工程化实践
  • css
  • javascript
  • es6常问
  • 手写代码
  • 错误监控
  • webpack
  • vue框架
  • 浏览器相关
  • 计算机网络
  • 数据结构和算法
  • 学习笔记
工具
  • css
  • javascript
  • 错误监控
  • ES6常问
  • 手写代码
    • 在非 new 调用时抛出错误
    • 防抖
    • 节流
    • 深拷贝(递归的方式实现)
    • 数组去重
    • ajax
    • 数组转树结构
    • treeToArr(树结构转数组)
    • compose
    • curry
    • dom2Json
    • eventBus(发布订阅)
    • instanceof
    • 继承
    • lazyman
    • new
    • Object.create
    • reduce
    • settimeout 实现 setinterval
    • 模板引擎实现
    • 对象,数组扁平化
    • bind,call,apply
    • promise
  • webpack
  • vue框架
  • 浏览器相关
  • 计算机网络
  • 数据结构和算法
  • 设计模式
  • interview
jason lee
2021-09-01
目录

手写代码

# 在非 new 调用时抛出错误

// 方式一
function Person(name) {
  if (!(this instanceof Person)) {
    throw Error("error msg");
  }
  this.name = name;
}
// 方式二
function f() {
  if (!new.target) {
    throw new Error("请使用 new 命令调用!");
  }
  // ...
}

f(); // Uncaught Error: 请使用 new 命令调用!

# 防抖

// 基础版
function debounce(func, wait) {
  var timer = null;
  return function() {
    clearTimeout(timer);
    timer = setTimeout(() => {
      func.apply(this, arguments);
    }, wait);
  };
}
// 完整版
function debounce1(func, wait, option = { leading: false, trailing: true }) {
  let timer = null;
  return function(...args) {
    let isInvoked = false;
    if (timer === null && option.leading) {
      func.call(this, ...args);
      isInvoked = true;
    }
    // 计时器需要重置
    window.clearTimeout(timer);
    timer = window.setTimeout(() => {
      if (option.trailing && !isInvoked) {
        func.call(this, ...args);
      }
      timer = null;
    }, wait);
  };
}

# 节流

// 方式一: 时间差
function throttle(func, wait) {
  var previous = 0;
  return function(...args) {
    var now = +new Date();
    console.log(now - previous, "222");
    if (now - previous > wait) {
      func.apply(this, args);
      previous = now;
    }
  };
}
// 方式二: 定时器
function throttle2(func, wait) {
  var args, timeout;
  return function() {
    args = arguments;
    if (!timeout) {
      timeout = setTimeout(() => {
        func.apply(this, args);
        timeout = null;
      }, wait);
    }
  };
}

# 深拷贝(递归的方式实现)

// 基础版(不考虑到循环引用和symbol)
function deepCopy(obj, hash = new WeakMap()) {
  if (typeof obj !== "object" || obj === null) return obj;
  var newObj = Array.isArray(obj) ? [] : {};
  for (const key in obj) {
    const element = obj[key];
    if (typeof element === "object" || element !== null) {
      newObj[key] = deepCopy(obj[key]);
    } else {
      newObj[key] = obj[key];
    }
  }
  return newObj;
}
// 完整版(考虑到循环引用和symbol)
function deepCopy(obj, hash = new WeakMap()) {
  if (typeof obj !== "object" || obj === null) return obj;

  if (hash.has(obj)) return hash.get(obj);
  var newObj = Array.isArray(obj) ? [] : {};
  hash.set(obj, newObj);
  var symbolKeys = Object.getOwnPropertySymbols(obj);
  symbolKeys.forEach((element) => {
    if (typeof obj[element] === "object" && obj[element] !== null) {
      newObj[element] = deepCopy(obj[element]);
    } else {
      newObj[element] = obj[element];
    }
  });
  for (const key in obj) {
    if (typeof obj[key] === "object" && obj[key] !== null) {
      newObj[key] = deepCopy(obj[key], hash);
    } else {
      newObj[key] = obj[key];
    }
  }
  return newObj;
}

# 数组去重

思路:声明一个新数组,遍历可能重复的数组,判断如果新数组中存在该元素,有就跳过,没有往新数组添加

// 方式1:new Set
function uniqueArr(arr) {
  return [...new Set(arr)];
}
// 方式2:new Map
function uniqueArr(arr) {
  const map = new Map();
  const res = [];
  for (let i = 0; i < arr.length; i++) {
    if (!map.has(arr[i])) {
      map.set(arr[i], true);
      res.push(arr[i]);
    }
  }
  return res;
}
// 方式3:es6中数组的方法(filter,include)
function uniqueArr(arr) {
  const res = [];
  for (let i = 0; i < arr.length; i++) {
    if (!res.includes(arr[i])) res.push(arr[i]);
  }
  return res;
}
// 方式4: indexOf
function uniqueArr(arr) {
  const res = [];
  for (let i = 0; i < arr.length; i++) {
    if (res.indexOf(arr[i]) === -1) res.push(arr[i]);
  }
  return res;
}

# ajax

注意点:兼容 ie 的话加上 new ActiveXObject('Mscrosoft.XMLHttp')

const _fetch = function(url, method = "get", formData) {
  return new Promise(function(resolve, reject) {
    const xhr = window.XMLHttpRequest
      ? new XMLHttpRequest()
      : new ActiveXObject("Mscrosoft.XMLHttp");
    xhr.open(method, url);
    xhr.send(formData);
    xhr.onreadystatechange = function() {
      //0:请求未初始化 1:服务器连接已建立 2:请求已接收 3:请求处理中 4:请求已完成,且响应已就绪
      if ((xhr.readyState === 4 && xhr.status === 200) || xhr.status === 304) {
        resolve(xhr.responseText);
      } else {
        reject(new Error(xhr.responseText));
      }
    };
  });
};

# 数组转树结构

var list = [
  { id: 1, name: "部门A", parentId: 0 },
  { id: 3, name: "部门C", parentId: 1 },
  { id: 4, name: "部门D", parentId: 1 },
  { id: 5, name: "部门E", parentId: 2 },
  { id: 6, name: "部门F", parentId: 3 },
  { id: 7, name: "部门G", parentId: 2 },
  { id: 8, name: "部门H", parentId: 4 },
];
function arrtotree(list) {
  const map = list.reduce((acc, item) => {
    acc[item.id] = item;
    return acc;
  }, {});
  const ans = [];
  for (let key in map) {
    var item = map[key];
    if (!item.parentId) {
      // 或者 item.parentId === 0
      ans.push(item);
    } else {
      var parent = map[item.parentId];
      if (parent) {
        parent.children = parent.children || [];
        parent.children.push(item);
      }
    }
  }
  return ans;
}
// 方法2
function listToTreeWithLevel(list, parent, level) {
  var out = [];
  for (var node of list) {
    if (node.parentId === parent) {
      node.level = level;
      var children = listToTreeWithLevel(list, node.id, level + 1);
      node.children = children;
      out.push(node);
    }
  }
  return out;
}
var result = arrtotree(list);
var result1 = listToTreeWithLevel(list, 0, 0);
console.log(result, result1);

# treeToArr(树结构转数组)

function treeToList(tree) {
  var queen = [];
  var out = [];
  queen = queen.concat(tree);
  while (queen.length) {
    var first = queen.shift();
    if (first.children) {
      queen = queen.concat(first.children);
      delete first["children"];
    }
    out.push(first);
  }
  return out;
}
var tree = {
  key: "10",
  name: "十",
  children: [
    {
      key: "5",
      name: "五",
    },
    {
      key: "2",
      name: "二",
      children: [
        {
          key: "7",
          name: "七",
        },
        {
          key: "11",
          name: "十一",
        },
      ],
    },
    {
      key: "3",
      name: "三",
    },
  ],
};
console.log(treeToList(tree));

# compose

function compose(...fn) {
  if (!fn.length) return (v) => v;
  if (fn.length === 1) return fn[0];
  return fn.reduce((pre, cur) => {
    return (...args) => pre(cur(...args));
  });
}
// 用法如下:
function fn1(x) {
  return x + 1;
}
function fn2(x) {
  return x + 2;
}
function fn3(x) {
  return x + 3;
}
function fn4(x) {
  return x + 4;
}
const a = compose(fn1, fn2, fn3, fn4);
console.log(a);
console.log(a(1)); // 1+4+3+2+1=11

# curry

function curry(fn) {
  return function curried(...args) {
    if (fn.length <= args.length) {
      return fn(...args);
    }
    return (...args1) => curried(...args, ...args1);
  };
}
const join = (a, b, c) => {
  return a + b + c;
};
// add(1)(2)(3)()=6 add(1,2,3)(4)()=10
const add = curry(join);
const res = add(1)(2)(3);
console.log(res);

# dom2Json

<div class="xxx">
  <span>
    <a>eee</a>
  </span>
  <span>
    <a>111</a>
    <a>222</a>
  </span>
</div>
function dom2Json(domtree) {
  let obj = {};
  obj.name = domtree.tagName;
  obj.children = [];
  domtree.childNodes.forEach((child) => obj.children.push(dom2Json(child)));
  return obj;
}
var xxx = document.querySelector(".xxx");
console.log(dom2Json(xxx));

# eventBus(发布订阅)

class EventEmitter {
  constructor() {
    this._events = {};
  }
  // 实现订阅
  on(eventName, callback) {
    if (!this._events) {
      // 给调用者增了个属性
      this._events = {};
    }
    if (this._events[eventName]) {
      this._events[eventName].push(callback);
    } else {
      this._events[eventName] = [callback];
    }
  }
  // 删除订阅
  off(eventName, callback) {
    if (this._events[eventName]) {
      this._events[eventName] = this._events[eventName].filter((fn) => {
        return fn != callback && fn.l !== callback;
      });
    }
  }
  // 只执行一次订阅事件
  once(eventName, callback) {
    function one() {
      callback(...arguments); // 面向切片
      this.off(eventName, one);
    }
    one.l = callback;
    this.on(eventName, one);
  }
  // 触发事件
  emit(eventName) {
    if (this._events[eventName]) {
      this._events[eventName].forEach((fn) => {
        fn.call(this, ...arguments);
      });
    }
  }
}
// 测试
const event = new EventEmitter();
const handle = (...rest) => {
  console.log(rest);
};
event.on("click", handle);
event.emit("click", 1, 2, 3, 4);
event.off("click", handle);
event.emit("click", 1, 2);
event.once("dbClick", handle);
event.emit("dbClick", "xx", "pp");
event.emit("dbClick");

# instanceof

function _instanceof(obj, target) {
  if (obj == null || typeof obj !== "object") return false;
  while (obj) {
    const proto = Object.getPrototypeOf(obj);
    if (proto === target.prototype) {
      return true;
    }
    obj = proto;
  }
  return false;
}
console.log(_instanceof(1, Object));

# 继承

实现 Student 方法,Student 继承 Person ,也有自己的属性和方法

function Student(grade, name) {
  Person.call(this);
  this.grade = grade;
  this.name = name;
}
Student.prototype = Object.create(Person.prototype);
Student.prototype.Constructor = Student;
Student.prototype.getGrade = function() {
  return this.grade;
};

# lazyman

class lazyMan {
  constructor(name) {
    this.name = name;
    this.subs = [];
    var fn = () => {
      console.log(name);
      this.next();
    };
    this.subs.push(fn);
    setTimeout(() => {
      this.next();
    }, 0);
  }
  eat() {
    var fn = () => {
      console.log("eat");
      this.next();
    };
    this.subs.push(fn);
    return this;
  }
  sleepFirst(ms) {
    var fn = () => {
      setTimeout(() => {
        console.log(ms + "Wake up after");
        this.next();
      }, ms);
    };
    this.subs.unshift(fn); // 放到任务队列顶部
    return this;
  }
  sleep(ms) {
    var fn = () => {
      setTimeout(() => {
        console.log(ms + "miao");
        this.next();
      }, ms);
    };
    this.subs.push(fn);
    return this;
  }
  next() {
    var fn = this.subs.shift();
    fn && fn();
  }
}
function _lazyMan(name) {
  return new lazyMan(name);
}
_lazyMan("lrg")
  .sleep(1000)
  .eat();

# new

function _new(constructor, ...rest) {
  var context = Object.create(constructor.prototype);
  var result = constructor.apply(context, rest);
  var isObj = typeof result === "object" && result !== null;
  return isObj ? result : context;
}
// 测试
function Person(name) {
  this.name = name;
}
var person = _new(Person, "jason");
console.log(person.name);

# Object.create

思路:将传入的对象作为原型

function _create(obj) {
  function F() {}
  F.prototype = obj.prototype || obj;
  return new F();
}
// 测试
var obj = { name: "jason" };
var newObj = _create(obj);
console.log(newObj);

# reduce

Array.prototype._reduce = function(...args) {
  const hasInitialValue = args.length > 1;
  if (!hasInitialValue && this.length === 0) {
    throw new Error();
  }
  let result = hasInitialValue ? args[1] : this[0];
  for (let i = hasInitialValue ? 0 : 1; i < this.length; i++) {
    result = args[0](result, this[i], i, this);
  }

  return result;
};
// 测试
var arr = [1, 2, 3];
const res = arr._reduce((lj, item) => {
  return lj + item;
}, 100);
console.log(res); // 106

# settimeout 实现 setinterval

function _setTimeOut(callBack, wait) {
  let timer = null;
  function interVal() {
    callBack();
    timer = setTimeout(() => {
      interVal();
    }, wait);
  }
  interVal();
  return {
    cancel: () => {
      clearTimeout(timer);
    },
  };
}
// 测试
_setTimeOut(() => {
  console.log("打印");
}, 1000);

# 模板引擎实现

// let template = '我是{{name}},年龄{{age}},性别{{sex}}';
// let data = {
//   name: '姓名',
//   age: 18
// }
// render(template, data); // 我是姓名,年龄18,性别undefined

function render(template, data) {
  const reg = /\{\{(\w+)\}\}/; // 模板字符串正则
  if (reg.test(template)) {
    // 判断模板里是否有模板字符串
    const name = reg.exec[template](1); // 查找当前模板里第一个模板字符串的字段
    template = template.replace(reg, data[name]); // 将第一个模板字符串渲染
    return render(template, data); // 递归的渲染并返回渲染后的结构
  }
  return template; // 如果模板没有模板字符串直接返回
}

# 对象,数组扁平化

// 数组扁平化
function arrFlat(arr, depth = 1) {
  var result = [];
  for (var i = 0; i < arr.length; i++) {
    if (Array.isArray(arr[i]) && depth > 0) {
      result = result.concat(arrFlat(arr[i], depth - 1));
    } else {
      result.push(arr[i]);
    }
  }
  return result;
}
console.log(arrFlat([1, 2, [1, [2, 3, [4, 5, [6]]]]]));

// 对象扁平化
// { a: b: c: { d: 1 }, aa: 2, c: [1, 2] } => { 'a.b.c.d': 1, aa: 2, 'c[0]': 1, 'c[1]': 2 }
function objFlat(obj) {
  var res = {};
  fn(obj);
  function fn(obj, str = "") {
    for (const key in obj) {
      const ele = obj[key];
      if (Object.prototype.toString.call(ele) === "[object Object]") {
        fn(obj[key], str + key + ".");
      } else {
        res[str + key] = ele;
      }
      if (Object.prototype.toString.call(ele) === "[object Array]") {
        for (var i in ele) {
          res[`${key}[${i}]`] = ele[i];
        }
      }
    }
  }
  return res;
}
console.log(
  objFlat({ a: { b: { c: { d: 1, name: "lrg" } } }, aa: 2, c: [1, 2] })
);

# bind,call,apply

call 思路:将函数设为对象的属性,执行该函数,删除该函数,再考虑传参的问题, 和返回值的问题

//call=================================================
Function.prototype._call = function(thisArg, ...args) {
  thisArg = thisArg || window; // 如果第一个参数为null,this指向window
  thisArg = Object(thisArg); //如果传进去的是一个基本类型的值,则会构造其包装类型的对象
  let func = Symbol(); // 避免重名
  thisArg[func] = this;
  let res = thisArg[func](...args);
  delete thisArg[func];
  return res;
};
// 测试
var foo = {
  name: "jason",
  age: 18,
};
function func(name, age) {
  return {
    name: this.name,
    age,
  };
}
var res = func._call(foo, "jason", 26);
console.log(res);

//apply=================================================
Function.prototype._apply = function(thisArg, ...args) {
  thisArg = thisArg || window;
  thisArg = Object(thisArg);
  let func = Symbol();
  thisArg[func] = this;
  let res = thisArg[func](args);
  delete thisArg[func];
  return res;
};
// 测试
var foo1 = {
  value: 1,
  value2: 2,
};
function func1(params) {
  return {
    value: this.value,
    value1: params,
  };
}
var res1 = func1._apply(foo1, [4, 2]);
console.log(res1);

// bind=============================================
Function.prototype._bind = function(ctx, ...args) {
  return (...innerArgs) => this.call(ctx, ...args, ...innerArgs);
};
// 测试
const a = {
  name: "jasonlee",
};
function test(...msg) {
  console.log(this.name);
}
const newFunc = test._bind(a, 1);
newFunc(2);

# promise

function _promise(executor) {
  // 给promise定义状态
  this.status = "pending";
  // 成功和失败的原因
  this.value = undefined;
  this.reason = undefined;
  let self = this;
  function reoslve(value) {
    if (self.status === "pending") {
      self.value = value;
      self.status = "fulfilled";
    }
  }
  function reject(reason) {
    if (self.status === "pending") {
      self.reason = reason;
      self.status = "rejected";
    }
  }
  // 执行器会立刻执行
  try {
    executor(reoslve, reject);
  } catch (e) {
    // 如果报错 调用then方法的失败方法即可
    reject(e);
  }
}

_promise.prototype.then = function(onfulfilled, onrejected) {
  let self = this;
  if (self.status === "fulfilled") {
    // 如果状态成功 则调用成功的回调
    onfulfilled(self.value);
  }
  if (self.status === "rejected") {
    // 如果状态是是失败 则调用失败的回调
    onrejected(self.reason);
  }
};
ES6常问
webpack

← ES6常问 webpack→

Theme by Vdoing | Copyright © 2019-2022 jason lee | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式