防抖函数

原理:事件被触发 n 秒后再执行回调,n 秒内又被触发,则从新计时(相似 lol 按 b 键回城)。

// 函数防抖的实现function debounce(fn, wait) {  let timer = null;  return function () {    let context = this,      args = arguments;    // 如果此时存在定时器的话,则勾销之前的定时器从新记时    if (timer) {      clearTimeout(timer);      timer = null;    }    // 设置定时器,使事件间隔指定事件后执行    timer = setTimeout(() => {      fn.apply(context, args);    }, wait);  };}

实用场景:

  1. 按钮提交场景:避免屡次提交按钮,只执行最初提交的一次
  2. 窗口滚动条的监听:滚动条触发次数太过频繁并且通常只须要获取最初一次的地位

在线运行 / 预览地址

节流函数

防抖函数原理:规定在一个单位工夫内,只能触发一次函数。如果这个单位工夫内触发屡次函数,只有一次失效。(lol 技能冷却)

应用工夫戳

// 函数节流的实现;function throttle(fn, delay) {  let curTime = Date.now();  return function () {    let context = this,      args = arguments,      nowTime = Date.now();    // 如果两次工夫距离超过了指定工夫,则执行函数。    if (nowTime - curTime >= delay) {      curTime = Date.now();      return fn.apply(context, args);    }  };}

应用标记

// 函数节流的实现;const throttle = (fn, delay = 500) => {  let flag = true;  return (...args) => {    if (!flag) return;    flag = false;    setTimeout(() => {      fn.apply(this, args);      flag = true;    }, delay);  };};

实用场景:

  1. 拖拽场景:固定工夫内只执行一次,避免超高频次触发地位变动
  2. 缩放场景:监控浏览器 resize
  3. 动画场景:防止短时间内屡次触发动画引起性能问题

在线运行 / 预览地址

深拷贝

//应用JSON解决办法const newObj = JSON.parse(JSON.stringify(Obj));

局限性:

  1. 函数对象会被转成 null 、RegExp 会变成{}
  2. 会摈弃对象的 constructor,所有的构造函数会指向 Object
  3. 对象有循环援用,会报错
//递归实现深拷贝function deepCopy(obj, dep = 0) {  if (typeof obj !== "object" || dep < 1) {    return typeof obj === "object" ? Object.assign({}, obj) : obj;  }  let clone = Array.isArray(obj) ? [] : {};  for (const key in obj) {    if (Object.hasOwnProperty.call(obj, key)) {      if (obj[key] && typeof obj[key] === "object") {        clone[key] = deepCopy(obj[key], dep - 1);      } else {        clone[key] = obj[key];      }    }  }  return clone;}

实现简略通俗易懂个别能写出这个就 ok 了

在线运行 / 预览地址

Object.create

function create(obj) {  function F() {}  F.prototype = obj;  return new F();}

ps:补充一个面试知识点 Object.create(null)能够创立一个不含原型链的污浊 Object 对象

instanceof

instanceof 运算符用于判断构造函数的 prototype 属性是否呈现在对象的原型链中的任何地位。

实现思路:

  1. 首先获取类型的原型
  2. 而后取得对象的原型
  3. 而后始终循环判断对象的原型是否等于类型的原型,直到对象原型为 null,因为原型链最终为 null
//instanceof的实现function myInstanceof(left, right) {  function myInstanceof(left, right) {    //获取对象原型    let __proto__ = Object.getPrototypeOf(left),      prototype = right.prototype; //获取构造函数的prototype    while (__proto__) {      if (__proto__ === prototype) {        return true;      }      __proto__ = Object.getPrototypeOf(__proto__);    }    return false;  }}

在线运行 / 预览地址

new 操作符

在调用 new 的过程中会产生四件事件(面试常问):

  1. 在堆去开拓一块内存创立了一个新的空对象
  2. 设置原型,将新对象的原型设置为函数的 prototype 对象。
  3. 让函数的 this 指向这个对象,执行构造函数的代码(为这个新对象增加属性)
  4. 判断函数的返回值类型,如果是值类型,返回创立的对象。如果是援用类型,就返回这个援用类型的对象。
function objectFactory(constructor, ...rest) {  let newObject = null;  let result = null;  // 判断参数是否是一个函数  if (typeof constructor !== "function") {    console.error("type error");    return;  }  // 新建一个空对象,对象的原型为构造函数的 prototype 对象  newObject = Object.create(constructor.prototype);  // 将 this 指向新建对象,并执行函数  result = constructor.apply(newObject, ...rest);  // 判断返回对象  let flag =    result && (typeof result === "object" || typeof result === "function");  // 判断返回后果  return flag ? result : newObject;}

在线运行 / 预览地址

Function.prototype.call 办法

call 函数的实现步骤:

  1. 判断调用对象是否为函数,即便咱们是定义在函数的原型上的,然而可能呈现应用 call 等形式调用的状况。
  2. 判断传入上下文对象是否存在,如果不存在,则为 window 。
  3. 解决传入的参数,截取第一个参数后的所有参数。
  4. 将函数作为上下文对象的一个属性。
  5. 应用上下文对象来调用这个办法,并保留返回后果。
  6. 删除方才新增的属性。
  7. 返回后果。
// call函数实现Function.prototype.myCall = function (context) {  // 判断调用对象  if (typeof this !== "function") {    console.error("type error");  }  // 获取参数  let args = [...arguments].slice(1),    result = null;  // 判断 context 是否传入,如果未传入则设置为 window  context = context || window;  // 将调用函数设为对象的办法  context.fn = this;  // 调用函数  result = context.fn(...args);  // 将属性删除  delete context.fn;  return result;};

Function.prototype.apply 办法

apply 函数的实现步骤:

  1. 判断调用对象是否为函数,即便咱们是定义在函数的原型上的,然而可能呈现应用 call 等形式调用的状况。
  2. 判断传入上下文对象是否存在,如果不存在,则设置为 window 。
  3. 将函数作为上下文对象的一个属性。
  4. 判断参数值是否传入
  5. 应用上下文对象来调用这个办法,并保留返回后果。
  6. 删除方才新增的属性
  7. 返回后果
// apply 函数实现Function.prototype.myApply = function (context) {  // 判断调用对象是否为函数  if (typeof this !== "function") {    throw new TypeError("Error");  }  let result = null;  // 判断 context 是否存在,如果未传入则为 window  context = context || window;  // 将函数设为对象的办法  context.fn = this;  // 调用办法  if (arguments[1]) {    result = context.fn(...arguments[1]);  } else {    result = context.fn();  }  // 将属性删除  delete context.fn;  return result;};

Function.prototype.bind 办法

bind 函数的实现步骤:

  1. 判断调用对象是否为函数,即便咱们是定义在函数的原型上的,然而可能呈现应用 call 等形式调用的状况。
  2. 保留以后函数的援用,获取其余传入参数值。
  3. 创立一个函数返回
  4. 函数外部应用 apply 来绑定函数调用,须要判断函数作为构造函数的状况,这个时候须要传入以后函数的 this 给 apply 调用,其余状况都传入指定的上下文对象。
// bind 函数实现Function.prototype.myBind = function (context) {  // 判断调用对象是否为函数  if (typeof this !== "function") {    throw new TypeError("Error");  }  // 获取参数  var args = [...arguments].slice(1),    fn = this;  return function Fn() {    // 依据调用形式,传入不同绑定值    return fn.apply(      this instanceof Fn ? this : context,      args.concat(...arguments)    );  };};

函数柯里化

柯里化就是把承受「多个参数」的函数变换成承受一个「繁多参数」的函数,并且返回承受「余下参数」返回后果的一种利用。

思路:

  1. 判断传递的参数是否达到执行函数的 fn 个数
  2. 没有达到的话,持续返回新的函数,并且返回 curry 函数传递残余参数
//实现1function curry(fn, args) {  // 获取函数须要的参数长度  let length = fn.length;  args = args || [];  return function () {    let subArgs = args.slice(0);    // 拼接失去现有的所有参数    for (let i = 0; i < arguments.length; i++) {      subArgs.push(arguments[i]);    }    // 判断参数的长度是否曾经满足函数所需参数的长度    if (subArgs.length >= length) {      // 如果满足,执行函数      return fn.apply(this, subArgs);    } else {      // 如果不满足,递归返回科里化的函数,期待参数的传入      return curry.call(this, fn, subArgs);    }  };}
//实现2function currying(exeFunc) {  let args = [];  let currFunc = function (...rest) {    args.push(...rest);    if (rest.length <= 0) {      return exeFunc(args);    } else {      return currFunc;    }  };  return currFunc;}// es6 实现function curry(fn, ...args) {  return fn.length <= args.length ? fn(...args) : curry.bind(null, fn, ...args);}

在线运行 / 预览地址

AJAX 申请

AJAX 是 Asynchronous JavaScript and XML 的缩写,指的是通过 JavaScript 的 异步通信,从服务器获取 XML 文档从中提取数据,再更新以后网页的对应局部,而不必刷新整个网页。

创立 AJAX 申请的步骤:

  1. 创立一个 XMLHttpRequest 对象。
  2. 在这个对象上应用 open 办法创立一个 HTTP 申请,open 办法所须要的参数是申请的办法、申请的地址、是否异步和用户的认证信息。
  3. 在发动申请前,能够为这个对象增加一些信息和监听函数。比如说能够通过 setRequestHeader 办法来为申请增加头信息。还能够为这个对象增加一个状态监听函数。一个 XMLHttpRequest 对象一共有 5 个状态,当它的状态变动时会触发 onreadystatechange 事件,能够通过设置监听函数,来解决申请胜利后的后果。当对象的 readyState 变为 4 的时候,代表服务器返回的数据接管实现,这个时候能够通过判断申请的状态,如果状态是 2xx 或者 304 的话则代表返回失常。这个时候就能够通过 response 中的数据来对页面进行更新了。
  4. 当对象的属性和监听函数设置实现后,最初调用 sent 办法来向服务器发动申请,能够传入参数作为发送的数据体。
const SERVER_URL = "/server";let xhr = new XMLHttpRequest();// 创立 Http 申请xhr.open("GET", SERVER_URL, true);// 设置状态监听函数xhr.onreadystatechange = function () {  if (this.readyState !== 4) return;  // 当申请胜利时  if (this.status === 200) {    handle(this.response);  } else {    console.error(this.statusText);  }};// 设置申请失败时的监听函数xhr.onerror = function () {  console.error(this.statusText);};// 设置申请头信息xhr.responseType = "json";xhr.setRequestHeader("Accept", "application/json");// 发送 Http 申请xhr.send(null);

在线运行 / 预览地址

A+标准的 promise

promise 是 Es6 新加的一个对象手写 promise 能帮忙更好的了解

var PromisePolyfill = (function () {  // 和reject不同的是resolve须要尝试开展thenable对象  function tryToResolve(value) {    if (this === value) {      // 次要是避免上面这种状况      // let y = new Promise(res => setTimeout(res(y)))      throw TypeError("Chaining cycle detected for promise!");    }    // 依据标准2.32以及2.33 对对象或者函数尝试开展    // 保障S6之前的 polyfill 也能和ES6的原生promise混用    if (      value !== null &&      (typeof value === "object" || typeof value === "function")    ) {      try {        // 这里记录这次then的值同时要被try包裹        // 次要起因是 then 可能是一个getter, 也也就是说        //   1. value.then可能报错        //   2. value.then可能产生副作用(例如屡次执行可能后果不同)        var then = value.then;        // 另一方面, 因为无奈保障 then 的确会像预期的那样只调用一个onFullfilled / onRejected        // 所以减少了一个flag来避免resolveOrReject被屡次调用        var thenAlreadyCalledOrThrow = false;        if (typeof then === "function") {          // 是thenable 那么尝试开展          // 并且在该thenable状态扭转之前this对象的状态不变          then.bind(value)(            // onFullfilled            function (value2) {              if (thenAlreadyCalledOrThrow) return;              thenAlreadyCalledOrThrow = true;              tryToResolve.bind(this, value2)();            }.bind(this),            // onRejected            function (reason2) {              if (thenAlreadyCalledOrThrow) return;              thenAlreadyCalledOrThrow = true;              resolveOrReject.bind(this, "rejected", reason2)();            }.bind(this)          );        } else {          // 领有then 然而then不是一个函数 所以也不是thenable          resolveOrReject.bind(this, "resolved", value)();        }      } catch (e) {        if (thenAlreadyCalledOrThrow) return;        thenAlreadyCalledOrThrow = true;        resolveOrReject.bind(this, "rejected", e)();      }    } else {      // 根本类型 间接返回      resolveOrReject.bind(this, "resolved", value)();    }  }  function resolveOrReject(status, data) {    if (this.status !== "pending") return;    this.status = status;    this.data = data;    if (status === "resolved") {      for (var i = 0; i < this.resolveList.length; ++i) {        this.resolveList[i]();      }    } else {      for (i = 0; i < this.rejectList.length; ++i) {        this.rejectList[i]();      }    }  }  function Promise(executor) {    if (!(this instanceof Promise)) {      throw Error("Promise can not be called without new !");    }    if (typeof executor !== "function") {      // 非标准 但与Chrome谷歌保持一致      throw TypeError("Promise resolver " + executor + " is not a function");    }    this.status = "pending";    this.resolveList = [];    this.rejectList = [];    try {      executor(tryToResolve.bind(this), resolveOrReject.bind(this, "rejected"));    } catch (e) {      resolveOrReject.bind(this, "rejected", e)();    }  }  Promise.prototype.then = function (onFullfilled, onRejected) {    // 返回值穿透以及谬误穿透, 留神谬误穿透用的是throw而不是return,否则的话    // 这个then返回的promise状态将变成resolved即接下来的then中的onFullfilled    // 会被调用, 然而咱们想要调用的是onRejected    if (typeof onFullfilled !== "function") {      onFullfilled = function (data) {        return data;      };    }    if (typeof onRejected !== "function") {      onRejected = function (reason) {        throw reason;      };    }    var executor = function (resolve, reject) {      setTimeout(        function () {          try {            // 拿到对应的handle函数解决this.data            // 并以此为根据解析这个新的Promise            var value =              this.status === "resolved"                ? onFullfilled(this.data)                : onRejected(this.data);            resolve(value);          } catch (e) {            reject(e);          }        }.bind(this)      );    };    // then 承受两个函数返回一个新的Promise    // then 本身的执行永远异步与onFullfilled/onRejected的执行    if (this.status !== "pending") {      return new Promise(executor.bind(this));    } else {      // pending      return new Promise(        function (resolve, reject) {          this.resolveList.push(executor.bind(this, resolve, reject));          this.rejectList.push(executor.bind(this, resolve, reject));        }.bind(this)      );    }  };  // for prmise A+ test  Promise.deferred = Promise.defer = function () {    var dfd = {};    dfd.promise = new Promise(function (resolve, reject) {      dfd.resolve = resolve;      dfd.reject = reject;    });    return dfd;  };  // for prmise A+ test  if (typeof module !== "undefined") {    module.exports = Promise;  }  return Promise;})();PromisePolyfill.all = function (promises) {  return new Promise((resolve, reject) => {    const result = [];    let cnt = 0;    for (let i = 0; i < promises.length; ++i) {      promises[i].then((value) => {        cnt++;        result[i] = value;        if (cnt === promises.length) resolve(result);      }, reject);    }  });};PromisePolyfill.race = function (promises) {  return new Promise((resolve, reject) => {    for (let i = 0; i < promises.length; ++i) {      promises[i].then(resolve, reject);    }  });};

在线运行 / 预览地址

千位分隔符

千位分隔符是解决日期罕用的形式

function parseToMoney(num) {  num = parseFloat(num.toFixed(3));  let [integer, decimal] = String.prototype.split.call(num, ".");  integer = integer.replace(/\d(?=(\d{3})+$)/g, "$&,");  return integer + "." + (decimal ? decimal : "");}

观察者模式

当对象间存在一对多关系时,则应用观察者模式(Observer Pattern)。比方,当一个对象被批改时,则会主动告诉依赖它的对象。vue 双向绑定就是应用了观察者模式。

class Subject {  constructor() {    this.ObserverList = [];  }  add(observer) {    this.ObserverList.push(observer);  }  remove(observer) {    this.ObserverList = this.ObserverList.filter((item) => item !== observer);  }  notify(...arg) {    this.ObserverList.forEach((cb) => cb.update(...arg));  }}class Observer {  constructor(name) {    this.name = name;  }  update() {    console.log(this.name);  }}

在线运行 / 预览地址

定义公布/订阅

公布/订阅模式:基于一个主题/事件通道,心愿接管告诉的对象(称为 subscriber)通过自定义事件订阅主题,被激活事件的对象(称为 publisher)通过公布主题事件的形式被告诉。vue2 中的全局事件总线就是应用这种设计模式。

/** * 公布/订阅模式组件 * @author  wilton */// 定义公布/订阅类class Pubsub {  constructor() {    this.topics = {};    this.subUid = -1;  }  // 公布事件  publish(topic, args) {    if (!this.topics[topic]) return false;    let subscribers = this.topics[topic];    let len = subscribers ? subscribers.length : 0;    while (len--) {      subscribers[len].func(topic, args);    }    return this;  }  // 订阅事件  subscribe(topic, func) {    if (!this.topics[topic]) this.topics[topic] = [];    let token = (++this.subUid).toString();    this.topics[topic].push({      token: token,      func: func,    });    return token;  }  // 勾销订阅  unsubscribe(token) {    for (let m in topics) {      if (topics[m]) {        for (let i = 0; i < topics[m].length; i++) {          if (topics[m][i].token == token) {            topics[m].splice(i, 1);            return token;          }        }      }    }    return this;  }}

在线运行 / 预览地址

应用 setTimeout 实现 setInterval

setInterval 的作用是每隔一段指定工夫执行一个函数,然而这个执行不是真的到了工夫立刻执行,它真正的作用是每隔一段时间将事件退出事件队列中去,只有当执行栈为空的时候,能力去从事件队列中取出事件执行。所以可能会呈现这样的状况,就是以后执行栈执行的工夫很长,导致事件队列里边积攒多个定时器退出的事件,当执行栈完结的时候,这些事件会顺次执行,因而就不能到距离一段时间执行的成果。
针对 setInterval 的这个毛病,咱们能够应用 setTimeout 递归调用来模仿 setInterval,这样咱们就确保了只有一个事件完结了,咱们才会触发下一个定时器事件,这样解决了 setInterval 的问题。

function mySetInterval(fn, timeout) {  setTimeout(() => {    fn();    mySetInterval(fn, timeout);  }, timeout);}

将 js 对象转化为树形构造

// 转换前:source = [{            id: 1,            pid: 0,            name: 'body'          }, {            id: 2,            pid: 1,            name: 'title'          }, {            id: 3,            pid: 2,            name: 'div'          }]// 转换为:tree = [{          id: 1,          pid: 0,          name: 'body',          children: [{            id: 2,            pid: 1,            name: 'title',            children: [{              id: 3,              pid: 1,              name: 'div'            }]          }        }]
 function jsonToTree(data) {  // 初始化后果数组,并判断输出数据的格局  let result = []  if(!Array.isArray(data)) {    return result  }  // 应用map,将以后对象的id与以后对象对应存储起来  let map = {};  data.forEach(item => {    map[item.id] = item;  });  //  data.forEach(item => {    let parent = map[item.pid];    if(parent) {      (parent.children || (parent.children = [])).push(item);    } else {      result.push(item);    }  });  return result;}

在线运行 / 预览地址

实现 sleep 函数

//应用Promise封装setTimeoufunction timeout(delay) {  return new Promise((resolve) => {    setTimeout(resolve, delay);  });  //应用工夫戳阻塞线程  function sleep(time, fn) {    let curDate = Date.now();    while (Date.now() - curDate < time);    fn();  }  sleep(3000, () => {    console.log(1);  });}

Es6 的 flat(数组扁平化)

数组扁平化就是将 [1, [2, [3]]] 这种多层的数组拍平成一层 [1, 2, 3]。

//数组扁平化let arr = [[22, [1, 2, [5, 8], [9, 2]], [2, 7, 1, [9, 0, [1, 6]]]], [2]];//1 reduce + concatfunction flatten(arr, dep) {  return dep > 1    ? arr.reduce(        (pre, cur) =>          pre.concat(Array.isArray(cur) ? flatten(cur, dep - 1) : cur),        []      )    : Array.prototype.slice.call(arr);}//2 some + concat + 构造function _flatten(arr, dep = 0) {  if (dep < 1) {    return arr.slice();  }  while (dep && arr.some((item) => Array.isArray(item))) {    arr = [].concat(...arr);    dep--;  }  return arr;}//3 pust + applyfunction __flatten(arr, dep = 0) {  if (dep < 1) {    return Array.prototype.slice.call(arr);  }  let result = [];  arr.forEach((element) =>    Array.isArray(element)      ? result.push.apply(result, __flatten(element, dep - 1))      : result.push(element)  );  return result;}//4 number onlyfunction ___flatten(arr) {  return arr    .toString()    .split(",")    .map((item) => +item);}//5 stackfunction ____flatten(arr, dep = 0) {  let stack = [...arr];  let result = [];  while (stack.length) {    let data = stack.pop();    if (Array.isArray(data)) {      stack.push(...data);    } else {      result.push(data);    }  }  return result.reverse();}

在线运行 / 预览地址

Object.assign

浅拷贝

Object.myAssign = function (target, ...source) {  if (target == null) {    throw new TypeError("Cannot convert undefined or null to object");  }  let ret = Object(target);  source.forEach(function (obj) {    if (obj != null) {      for (let key in obj) {        if (obj.hasOwnProperty(key)) {          ret[key] = obj[key];        }      }    }  });  return ret;};

实现每隔一秒打印 1,2,3,4

var 作用域问题会导致始终输入最初一个数字

// 应用闭包实现for (var i = 0; i < 5; i++) {  (function (i) {    setTimeout(function () {      console.log(i);    }, i * 1000);  })(i);}// 应用 let 块级作用域for (let i = 0; i < 5; i++) {  setTimeout(function () {    console.log(i);  }, i * 1000);}

js 解析 URL Params 为对象

let url =  "http://www.domain.com/?user=anonymous&id=123&id=456&city=%E5%8C%97%E4%BA%AC&enabled";parseParam(url);/* 后果{ user: 'anonymous',  id: [ 123, 456 ], // 反复呈现的 key 要组装成数组,能被转成数字的就转成数字类型  city: '北京', // 中文需解码  enabled: true, // 未指定值得 key 约定为 true}*/function parseParam(url) {  const paramsStr = /.+\?(.+)$/.exec(url)[1]; // 将 ? 前面的字符串取出来  const paramsArr = paramsStr.split("&"); // 将字符串以 & 宰割后存到数组中  let paramsObj = {};  // 将 params 存到对象中  paramsArr.forEach((param) => {    if (/=/.test(param)) {      // 解决有 value 的参数      let [key, val] = param.split("="); // 宰割 key 和 value      val = decodeURIComponent(val); // 解码      val = /^\d+$/.test(val) ? parseFloat(val) : val; // 判断是否转为数字      if (paramsObj.hasOwnProperty(key)) {        // 如果对象有 key,则增加一个值        paramsObj[key] = [].concat(paramsObj[key], val);      } else {        // 如果对象没有这个 key,创立 key 并设置值        paramsObj[key] = val;      }    } else {      // 解决没有 value 的参数      paramsObj[param] = true;    }  });  return paramsObj;}

实现一个 JSON.stringify

  • Boolean | Number| String 类型会主动转换成对应的原始值。
  • undefined、任意函数以及 symbol,会被疏忽(呈现在非数组对象的属性值中时),或者被转换成 null(呈现在数组中时)。
  • 不可枚举的属性会被疏忽
  • 如果一个对象的属性值通过某种间接的形式指回该对象自身,即循环援用,属性也会被疏忽。
  • Function 会变成 null RegExp 对象会变成{}

    function jsonStringify(obj) {let type = typeof obj;if (type !== "object") {  if (/string|undefined|function/.test(type)) {    obj = '"' + obj + '"';  }  return String(obj);} else {  let json = [];  let arr = Array.isArray(obj);  for (let k in obj) {    let v = obj[k];    let type = typeof v;    if (/string|undefined|function/.test(type)) {      v = '"' + v + '"';    } else if (type === "object") {      v = jsonStringify(v);    }    json.push((arr ? "" : '"' + k + '":') + String(v));  }  return (arr ? "[" : "{") + String(json) + (arr ? "]" : "}");}}

    在线运行 / 预览地址

实现 JSON.parse

let json = '{"name":"cxk", "age":25}';let obj = eval("(" + json + ")");

应用 eval 实现简略但很容易被 xss 攻打

js 的继承

继承有很多形式这里只演绎最优的两种寄生组合式和 es6 的 class 继承

//寄生组合式继承function inheritPrototype(subType, superType) {  // 创建对象,创立父类原型的一个正本  let prototype = Object.create(superType.prototype);  // constructor属性为子类构造函数  prototype.constructor = subType;  // 指定对象,将新创建的对象赋值给子类的原型  subType.prototype = prototype;}//es6 class的extendsclass Rectangle {  // constructor  constructor(height, width) {    this.height = height;    this.width = width;  }  // Getter  get area() {    return this.calcArea();  }  // Method  calcArea() {    return this.height * this.width;  }}const rectangle = new Rectangle(40, 20);console.log(rectangle.area);// 输入 800// 继承class Square extends Rectangle {  constructor(len) {    // 子类没有this,必须先调用super    super(len, len);    // 如果子类中存在构造函数,则须要在应用“this”之前首先调用 super()。    this.name = "SquareIng";  }  get area() {    return this.height * this.width;  }}const square = new Square(20);console.log(square.area);// 输入 400//extends继承的外围代码如下,其实和寄生组合式继承形式一样function _inherits(subType, superType) {  // 创建对象,创立父类原型的一个正本  // 加强对象,补救因重写原型而失去的默认的constructor 属性  // 指定对象,将新创建的对象赋值给子类的原型  subType.prototype = Object.create(superType && superType.prototype, {    constructor: {      value: subType,      enumerable: false,      writable: true,      configurable: true,    },  });  if (superType) {    Object.setPrototypeOf      ? Object.setPrototypeOf(subType, superType)      : (subType.__proto__ = superType);  }}

在线运行 / 预览地址

判断对象是否存在循环援用

循环援用对象原本没有什么问题,然而序列化的时候就会产生问题,比方调用 JSON.stringify()对该类对象进行序列化,就会报错: Converting circular structure to JSON.

const isCycleObject = (obj, parent) => {  const parentArr = parent || [obj];  for (let i in obj) {    if (typeof obj[i] === "object") {      let flag = false;      parentArr.forEach((pObj) => {        if (pObj === obj[i]) {          flag = true;        }      });      if (flag) return true;      flag = isCycleObject(obj[i], [...parentArr, obj[i]]);      if (flag) return true;    }  }  return false;};

在线运行 / 预览地址

数组的去重

数组去重也是罕用的解决数组面试题

//双重循环function unique(array) {  // res用来存储后果  var res = [];  for (var i = 0, arrayLen = array.length; i < arrayLen; i++) {    for (var j = 0, resLen = res.length; j < resLen; j++) {      if (array[i] === res[j]) {        break;      }    }    // 如果array[i]是惟一的,那么执行完循环,j等于resLen    if (j === resLen) {      res.push(array[i]);    }  }  return res;}//es6 setfunction unique(arr) {  return [...new Set(arr)];}//filter + indexOffunction unique_(arr) {  return arr.filter((item, index) => arr.indexOf(item) === index);}//reduce + includesfunction unique__(arr) {  return arr.reduce(    (pre, cur) => (pre.includes(cur) ? pre : pre.concat(cur)),    []  );}//mapfunction unique___(arr) {  let mp = new Map();  arr.forEach((element) => {    if (!mp.has(element)) {      mp.set(element, 1);    }  });  return [...mp.keys()];}//排序后function unique(array) {  var res = [];  var sortedArray = array.slice().sort();  var seen;  for (var i = 0, len = sortedArray.length; i < len; i++) {    // 如果是第一个元素或者相邻的元素不雷同    if (!i || seen !== sortedArray[i]) {      res.push(sortedArray[i]);    }    seen = sortedArray[i];  }  return res;}

在线运行 / 预览地址

图片懒加载

图片懒加载就是鼠标滑动到哪里,图片加载到哪里。总的来说,个别页面关上,会同时加载页面所有的图片,如果页面的图片申请太多会造成很卡很慢的景象,为了防止这一景象,利用懒加载图片的办法,进步性能

let imgList = [...document.querySelectorAll("img")];let length = imgList.length;const imgLazyLoad = (function () {  let count = 0;  return function () {    let deleteIndexList = [];    imgList.forEach((img, index) => {      let rect = img.getBoundingClientRect();      if (rect.top < window.innerHeight) {        img.src = img.dataset.src;        deleteIndexList.push(index);        count++;        if (count === length) {          document.removeEventListener("scroll", imgLazyLoad);        }      }    });    imgList = imgList.filter((img, index) => !deleteIndexList.includes(index));  };})();// 加上防抖解决document.addEventListener("scroll", debounce(imgLazyLoad, 200));

洗牌算法

打乱数组开发场景中也是很罕用

function shuffle(arr) {  for (leti = 0; i < arr.length; i++) {    let randomIndex = i + Math.floor(Math.random() * (arr.length - i));    [arr[i], arr[randomIndex]] = [arr[randomIndex], arr[i]];  }  return arr;}

在线运行 / 预览地址