Skip to content
js
let a = 0,
    b = 1,
    c = b.value?.v;
a ||= 10;
b &&= 2;
c ??= 5;
alert(a + b + c); // 17
js
// 连续赋值

var a = 3;
var b = a = 5;
/**
 * 等号赋值的方向从右到左
 *  var b = a = 5 =>  a = 5; b = a
 */

var a = { n: 1 };
var b = a;
/**
 * js运算中 . 和 = 同时出现时优先执行 .
 * so a.x = { n: 2 } => a = { n: 1, x: { n: 2 } }
 * 最后 a 被重新赋值 a = { n: 2 }
 * 此时 a = { n: 2 } b = { n: 1, x: { n: 2 } }
 */
a.x = a = { n: 2 };

基础知识

隐式转换

js
const add = new Proxy(
    { sum: 0, [Symbol.toStringTag]: 'Test' },
    {
        get(target, prop, receiver) {
            if (prop === Symbol.toPrimitive) {
                const temp = target.sum;
                target.sum = 0; // 为了不影响下次调用 需要清零
                return () => temp; // 隐式转换会触发 ====> [Symbol.toPrimitive] 函数,所以需要返回一个函数
            } else {
                target.sum += Number(prop);
                return receiver;
            }
        }
    }
);

const r1 = add[1][2][3] + 4;
const r2 = add[100][200] + 300;
const r3 = add[1000][2000][3000] + 4000;
console.log(r1, r2, r3);

/**
   * 如果没有 toString 方法调用,会返回 [object Object]
   * [Symbol.toStringTag] 会在调用 Object.prototype.toString.call 时返回(返回值必须是 string 类型)
   * 为什么加 get? ====> 因为 toString 方法在调用 [Symbol.toStringTag] 不会执行这个方法而是要这个属性的值 Array 身上没有这个属性 Map Set 有
   */

const obj = { [Symbol.toStringTag]: 'Text' };
const obj = { get [Symbol.toStringTag]() { return 'Text'} };
obj[Symbol.toStringTag] = 'Graph'

obj.__defineGetter__(Symbol.toStringTag, function () {
  return 'Graph';
});

obj.defineProperty(graph, Symbol.toStringTag, {
  // get() {
  //   return 'Graph';
  // },
  value: 'Graph'
});

console.log(Object.prototype.toString.call(obj)) // [object Text]

call apply bind 与 arguments.callee 的理解

js
function F1(name) {
    this.name = name;
}
F1.prototype.getName = function (age, gender) {
    const arr = Array.prototype.slice.call(arguments, 1); // 相当于 [...arguments].slice(1)
    console.log(arr);
    return { name: this.name, age, gender };
};

const obj = { name: 'CT' };
const f = new F1('HJ');

console.log(f.getName.call(f, 30, 'male'));
console.log(f.getName.call(obj, 30, 'female'));

console.log(f.getName.apply(f, [30, 'male']));
console.log(f.getName.apply(obj, [30, 'female']));

console.log(f.getName.bind(f, 30, 'male')());
console.log(f.getName.bind(obj, 30, 'female')());

// 对于一个匿名函数,要实现递归的话,可以使用 arguments.callee, 严格模式下会报错
const incrementFn = (function (num) {
    // 'use strict';
    console.log(num);
    return num < 100 ? arguments.callee(++num) : num;
})(1);
console.log(incrementFn);

原型链

  • 构造函数
js
function Person(name, age) {
    this.name = name;
    this.age = age;
}
function Student(name, age, score) {
    Person.call(this, name, age);
    this.score = score;
}
// 设置实例对象的原型
Student.prototype = Object.create(Person.prototype);
Student.prototype.constructor = Student;
// 设置构造函数的继承
Object.setPrototypeOf(Student, Person);
  • class
js
class Person {
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }
}

class Student extends Person {
  constructor(name, age, score) {
    super(name, age);
    this.score = score;
  }
}
const stu = new Student('HJ', 28, 99);
console.log(stu.__proto__ === Student.prototype);
console.log(Student.prototype.__proto__ === Person.prototype);
console.log(Person.prototype.__proto__ === Object.prototype);

console.log(Student.__proto__ === Person);
console.log(Person.__proto__ === Function.prototype);
console.log(Function.prototype === Function.__proto__);
// Function.prototype 是 构造函数 Object 的实例对象 即 Function.prototype instanceof Object
console.log(Function.prototype.__proto__ === Object.prototype);

console.log(Object.prototype.__proto__ === null);

// 结论:Object 与 Function 互为实例对象,并且 Function.prototype 也是 Object的实例对象
console.log(Object.__proto__ === Function.prototype);
  • 面试题
js
Function.prototype.a = 1;
Object.prototype.b = 2;

function A() {}
const a = new A();

console.log(A.a, A.b);
console.log(a.a, a.b);
console.log(Function.b, Object.a);
console.log(Function.a, Object.b);
js
const data = { a: { b: 1, c: 2, d: { e: 5 } }, b: [1, 3, { a: 2, b: 3 }], c: 3 };
// { 'a.b': 1, 'a.c': 2, 'a.d.e': 5, 'b[0]': 1, 'b[1]': 3, 'b[2].a': 2, 'b[2].b': 3, c: 3 };

function flatten(obj, key = '', result = {}) {
    for (const el in obj) {
        const element = obj[el];
        const newKey = key ? (Array.isArray(obj) ? `${key}[${el}]` : `${key}.${el}`) : el;
        typeof element === 'object' ? flatten(element, newKey, result) : (result[newKey] = element);
    }
    return result;
}

递归

js
function sum(num) {
    return num === 1 ? num : sum(num - 1) + num;
}
console.log(sum(100));
// 0 1 1 2 3 5 8
function fib(n) {
    return n < 3 ? (n === 1 ? 0 : 1) : fib(n-1) + fib(n-2)
}
for (let i = 1; i < 30; i++) {
    console.log(fib(i));
}
// 深拷贝
let obj = {
    name: 'HJ',
    age: 28,
    gender: null,
    info: {
        hobby: ['fitness', 'code', { a: 1 }],
        career: {
            teacher: 4,
            school: '固始一中',
            salary: new Date(),
            b: undefined,
            c: function () {},
            d: null
        }
    }
};

function deepClone(origin, target = {}) {
    for (const key in origin) {
        const element = origin[key];
        const type = Object.prototype.toString.call(element);
        if (Object.hasOwnProperty.call(origin, key)) {
            if (typeof element === 'object' && element !== null) {
                switch (type) {
                    case '[object Array]':
                        target[key] = [];
                        break;
                    case '[object Date]':
                        target[key] = new Date();
                        break;
                    default:
                        target[key] = {};
                }
                deepClone(origin[key], target[key]);
            } else {
                target[key] = origin[key];
            }
        }
    }
    return target;
}
console.log(obj);
console.log(deepClone(obj));
console.log(JSON.parse(JSON.stringify(obj)));

递归与回调的综合使用

js
const a = [1, 2, 3, [4, 5, 6, [7, 8, 9, 12, [5, [2]]]], [10, 12]];
const b = [2, 5, 3, 9, 10, 12];
const res = [2, [5, [9, [12]]], [11]];


function toArr1(source, target, result = []) {
  for (const item of source) {
    target.includes(item) && result.push(item);
    const subArr = Array.isArray(item) && toArr1(item, target);
    subArr.length && result.push(subArr);
  }
  return result;
}

function toArr2(source, target, cb = (item, result) => result.push(item), result = []) {
  for (const item of source) {
    if (target.includes(item)) cb(item, result);
    if (Array.isArray(item)) {
      const subArr = toArr2(item, target, cb);
      subArr.length && result.push(subArr);
    }
  }
  return result;
}

防抖与节流

js
function debounce(fn, delay = 2000, immediate = false) {
    let timer = null;
    return function () {
        const args = Array.prototype.slice.call(arguments),
            context = this;
        timer && clearTimeout(timer);
        console.log(timer);
        if (immediate) {
            const triggerNow = !timer;
            if (triggerNow) {
                fn.apply(context, args);
            }
            timer = setTimeout(() => {
                timer = null;
            }, delay);
        } else {
            timer = setTimeout(() => {
                fn.apply(context, args);
            }, delay);
        }
    };
}

function throttle(fn, delay) {
  // let prev = Date.now();
  // return function () {
  //   const args = Array.prototype.slice.call(arguments),
  //     context = this,
  //     now = Date.now();
  //   if (now - prev >= delay) {
  //     fn.apply(context, args);
  //     prev = Date.now();
  //   }
  // };
  let timer = null;
  return function () {
    const args = Array.prototype.slice.call(arguments),
      context = this;
    if (!timer) {
      timer = setTimeout(() => {
        fn.apply(context, args);
        timer = null;
      }, delay);
    }
  };
}

深度遍历(DFS)与广度遍历(BFS)

js
const data = [
  {
    name: 'a',
    children: [
      { name: 'b', children: [{ name: 'e' }] },
      { name: 'c', children: [{ name: 'f' }] },
      { name: 'd', children: [{ name: 'g' }] }
    ]
  },
  {
    name: 'a2',
    children: [
      { name: 'b2', children: [{ name: 'e2' }] },
      { name: 'c2', children: [{ name: 'f2' }] },
      { name: 'd2', children: [{ name: 'g2' }] }
    ]
  }
];
// 深度遍历, 使用递归 a,b,e,c,f,d,g,a2,b2,e2,c2,f2,d2,g2
function getName1(data, result = []) {
  data.forEach(el => {
    const { name, children } = el;
    result.push(name);
    children?.length && getName1(children, result);
  });
  return result.join(',');
}

// 广度遍历, 创建一个执行队列, 当队列为空的时候则结束 a,a2,b,c,d,b2,c2,d2,e,f,g,e2,f2,g2
function getName2(data, result = []) {
  while (data.length > 0) {
    [...data].forEach(item => {
      const { name, children } = item;
      console.log(data.shift());
      result.push(name);
      children?.length && data.push(...children);
    });
  }
  return result.join(',');
}

数据结构树形化

js
const menuRouter = [
    { id: 1, title: '首页', path: '/', icon: 'el-icon-s-home', pid: 0 },
    { id: 2, title: '新增', path: '/add', icon: 'el-icon-circle-plus', pid: 0 },
    { id: 3, title: '导航一', path: '/test1', icon: 'el-icon-setting', pid: 0 },
    { id: 4, title: '导航二', path: '/test2', icon: 'el-icon-location', pid: 0 },

    { id: 5, title: '导航三', path: '/test3', icon: 'el-icon-setting', pid: 0 },
    { id: 20, title: '导航三', path: '/test3', icon: 'el-icon-setting', pid: 5 },
    { id: 6, title: '分组一', pid: 5 },
    { id: 7, title: '选项一', path: '/test3/group1/1', pid: 6 },
    { id: 8, title: '选项一', path: '/test3/group1/2', pid: 6 },
    { id: 10, title: '分组一', pid: 9 },

    { id: 9, title: '导航四', path: '/test4', icon: 'el-icon-location', pid: 0 },

    { id: 13, title: '分组二', pid: 9 },
    { id: 14, title: '选项三', path: '/test5/group2/1', pid: 13 },
    { id: 15, title: '选项四', path: '/test5/group2/2', pid: 13 },
    { id: 16, title: '选项四', path: '/test5/group2/2/1', pid: 15 },

    { id: 17, title: '选项五', path: '/test5/3', pid: 9 },

    { id: 11, title: '选项一', path: '/test5/group1/1', pid: 10 },
    { id: 12, title: '选项二', path: '/test5/group1/2', pid: 10 },

    { id: 18, title: '权限管理', path: '/test2', icon: 'el-icon-user-solid', pid: 0 },
    { id: 19, title: '回收站', path: '/test7', icon: 'el-icon-delete-solid', pid: 0 }
];
  • 方法一
js
function formateDataTree1(menuRouter, pid, result = []) {
  menuRouter.forEach(item => {
    if (item.pid === pid) {
      const newItem = { ...item, children: [] };
      result.push(newItem);
      formateDataTree1(menuRouter, newItem.id, newItem.children);
    }
  });
  return result;
}

function formateDataTree2(menuRouter) {
  const parent = menuRouter.filter(item => item.pid === 0);
  function getChildren(parent, menuRouter) {
    parent.forEach(p => {
      menuRouter.forEach(c => {
        if (p.id === c.pid) {
          p.children ? p.children.push(c) : (p.children = [c]);
          getChildren([c], menuRouter);
        }
      });
    });
  }
  getChildren(parent, menuRouter);
  return parent;
}

function formatDataTree3(data) {
  const result = [];
  const map = {};
  for (const item of data) {
    const { id, pid } = item;
    map[id] = map[id] ? { ...item, children: map[id].children } : { ...item };
    const treeItem = map[id];
    if (pid === 0) {
      result.push(treeItem);
    } else {
      // 虚拟父级 用于处理子级在父级前面的情况
      map[pid] ??= { children: [] };
      // 用于判断是否有子集的情况
      map[pid].children ??= [];
      map[pid].children.push(treeItem);
      // map[pid].children ? map[pid].children.push(treeItem) : (map[pid].children = [treeItem]);
    }
  }
  return result;
}

function formateDataTree4(menuRouter) {
  return menuRouter.filter(p => {
    const children = menuRouter.filter(c => p.id === c.pid);
    children.length && (p.children = children);
    return p.pid === 0;
  });
}

树形结构扁平化

js
// 深度优先
function flattenArray1(data, result = []) {
    data.forEach(item => {
        const { children, ...others } = item;
        result.push(others);
        Array.isArray(children) && flattenArray1(children, result);
    });
    return result;
}
// 广度优先
function flattenArray2(tree, result = []) {
    while (tree.length > 0) {
        [...tree].forEach(item => {
            const { children, ...others } = item;
            tree.shift();
            result.push(others);
            children?.length && tree.push(...children);
        });
    }
    return result;
}

sku算法

  • reduce 的实现
js
Array.prototype.myReduce = function (callback, initialValue) {
  initialValue = initialValue || this[0];
  for (let index = initialValue ? 0 : 1; index < this.length; index++) {
    const element = this[index];
    initialValue = callback(initialValue, element, index, this);
  }
  return initialValue;
};
  • sku 实现
js
const color = ['深空灰', '银色'];
const inch = ['13', '16'];
const memory = ['8GB', '16Gb'];
const storage = ['256GB SSD', '512GB SSD', '1TB SSD', '2TB SSD'];

function combine() {
    const args = Array.prototype.slice.call(arguments);
    // 方法一
    // let res = [];
    // function helper(index = 0, prev = []) {
    //   const arr = args[index];
    //   const isLast = args.length - 1 === index;
    //   for (const el of arr) {
    //     const cur = prev.concat(el); // prev 不能直接更改
    //     isLast && console.log(cur);
    //     isLast ? res.push(cur) : helper(index + 1, cur);
    //   }
    // }
    // helper();
    // return res;

    // 方法二 cartesianProduct 2 * 2 * 2 * 4
    return args.reduce((initialValue, el) => {
        const sku = [];
        el.forEach(item => {
            initialValue.forEach(k => {
                sku.push(k.concat(item));
            });
        });
        return sku;
    }, [[]]);
};

console.log(combine(color, inch, memory, storage));

lodash get

js
const obj = {
  a: {
    b: {
      c: 123,
      d: {
        e: 456
      }
    }
  }
};
  • reduce 实现
js
function getValueByReduce(obj, str) {
  const arr = str.split('.');
  return arr.reduce((obj, item) => obj[item], obj);
}
  • 递归实现
js
function getValue(obj, str) {
  const arr = str.split('.');
  // arr.forEach(key => {
  //   obj = obj[key];
  // });
  // return obj;
  function get(obj, arr) {
    const result = obj[arr[0]];
    return arr.slice(1).length ? get(result, arr.slice(1)) : result;
  }
  return get(obj, arr);
}

矩阵数组转置

js
let list = [
  ['腾讯', '百度', '阿里巴巴', '美团'],
  [100, 200, 300, 400],
  [10000, 20000, 30000, 40000],
  [9, 99, 999, 9999],
  [6, 66, 666, 6666]
]; // 如何把 5 4 变成 4 5 
let header = ['公司', '2020-12-31', '2021-06-30', '成分', '流利程度'];

function getCompanyList(keyArr, valueArr) {
  // 方法二 只适用于处理完整数据的
  // let count = 0;
  // const result = [];
  // valueArr.forEach((el, i) => {
  //   el.forEach(item => {
  //     const index = count++ % el.length;
  //     // console.log(i);
  //     result[index] ??= {};
  //     // console.log(result);
  //     result[index][keyArr[i]] = item;
  //   });
  // });
  // console.log(result);

  // 方法一
  return valueArr.reduce((result, el, index) => {
    el.forEach((value, i) => {
      result[i] ??= {};
      result[i][keyArr[index]] = value;
    });
    return result;
  }, []);
}

分组

js
fetch('http://www.test.com/api/get_transfer_list')
  .then(res => res.json())
  .then(res => {
    const { transfer_list: transferList } = res.data;
    return groupByMonth(transferList);
  })
  .then(value => {
    console.log(value)
  });

function groupByMonth(data) {
  const map = {};
  data
    .sort((a, b) => new Date(b.transfer_time) - new Date(a.transfer_time))
    .forEach(item => {
      const date = new Date(item.transfer_time),
        year = date.getFullYear(),
        month = date.getMonth() + 1,
        time = `${year}${month}`;
      map[time] ??= { time, data: [] };
      map[time].data.push(item);
      // map[time] ? map[time].data.push(item) : (map[time] = { time: time, data: [item] });
    });
  return Object.values(map);
}

Echarts 折线图数据处理

js
fetch('http://www.test.com/api/course')
  .then(res => res.json())
  .then(res => {
    console.log(handleEchartData(res));
  });

function handleEchartData(data) {
  const map = [];
  for (const time in data) {
    const element = data[time];
    element.forEach((item, index) => {
      const { title } = item;
      map[index] = map[index] ?? { time, title, type: 'line', data: [] };
      map[index].data.push(item);
      // map[index]
      //   ? map[index].data.push(item)
      //   : (map[index] = { time, title, type: 'line', data: [item] });
    });
  }
  return map;
}
js
let url =
  'http://witmax.cn/index.php?user=name&id1=123&id2=456&id5=789&city=%E6%B7%B1%E5%9C%B3&disabled=true';
function getParamsObj(uri, keyArray = []) {
  const obj = Object.fromEntries(
    decodeURI(url)
      .split('?')
      .pop()
      .split('&')
      .map(item => item.split('='))
  );
  return Object.keys(obj).reduce((result, key) => {
    let newKey, index;
    const flag = keyArray.some(item => {
      newKey = item;
      index = key.slice(item.length) - 1;
      return key.startsWith(item);
    });
    if (flag) {
      result[newKey] ??= [];
      result[newKey][index] = obj[key];
    } else {
      result[key] = obj[key];
    }
    return result;
  }, {});
}

console.log(getParamsObj(url));

const str = 'abcabcadefb';
function getMaxCharacter(str) {
  const map = {};
  for (const char of str) {
    map[char] = map[char] ? ++map[char] : 1;
  }
  const max = Math.max(...Object.values(map));
  const result = Object.keys(map).filter(item => map[item] === max);
  return `当前最大值为${result.join(',')},出现次数为${max}`;
}

console.log(getMaxCharacter(str));

Released under the MIT License | Copyright © 2022-present HOUJIAN