Appearance
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));