# new 实现
function myNew() {
// 获取构造函数
const constr = Array.prototype.shift.call(arguments);
if (typeof constr !== 'function') {
throw new TypeError('Constructor Type Error');
}
// 创建对象 obj,并把 obj 的原型指向构造函数的原型对象上
const obj = Object.create(constr.prototype);
// 执行构造函数中的代码(为这个新对象添加属性)
const result = constr.apply(obj, arguments);
// 如果构造函数有返回值,则返回;否则,就会默认返回新对象。
return result instanceof Object ? result : obj;
}
# instanceof 实现
function myInstanceof(left, right) {
// 基本数据类型都返回false
if (typeof left !== 'object' || left === null) return false;
// 获取当前对象原型
let proto = Object.getPrototypeOf(left);
while (true) {
if (proto === null) return false;
if (proto === right.prototype) return true;
// 未找到再获取上层原型
proto = Object.getPrototypeOf(proto);
}
}
# debounce 防抖
所谓防抖,就是指触发事件后 n 秒后才执行函数,如果在 n 秒内又触发了事件,则会重新计算函数执行时间
<!-- 非立即执行版本 -->
function debounce(fn, wait) {
if (typeof fn !== 'function') {
throw new TypeError('Expected a function');
}
wait = Number(wait) || 0;
let timeout;
return function() {
// 获取 this 和 参数
const cxt = this;
const args = [...arguments];
// 定时器存在,就清除前面一个定时器,并开启一个新的定时器
if (timeout) clearTimeout(timeout);
timeout = setTimeout(() => {
fn.apply(cxt, args);
}, wait);
}
}
<!-- 立即执行版本 -->
// 触发事件后函数会立即执行
// n 秒内触发事件不会执行功能函数下一次调用
// n 秒后再次触发才会再次执行功能函数
function debounce(func, wait) {
let timeout;
return function () {
const cxt = this;
const args = [...arguments];
if (timeout) clearTimeout(timeout);
// 无定时器立刻执行标识
const immediate = !timeout;
timeout = setTimeout(() => {
timeout = null;
}, wait);
if (immediate) func.apply(cxt, args);
};
}
<!-- 合并版本 -->
function debounce(func, wait, immediate) {
let timeout;
return function () {
const cxt = this;
const args = [...arguments];
if (timeout) clearTimeout(timeout);
if (immediate) {
const nowFlag = !timeout;
timeout = setTimeout(() => {
timeout = null;
}, wait)
if (nowFlag) func.apply(cxt, args)
} else {
timeout = setTimeout(() => {
func.apply(cxt, args)
}, wait);
}
}
}
# throttle 节流
所谓节流,就是指连续触发事件但是在 n 秒中只执行一次函数。节流会稀释函数的执行频率
<!-- 时间戳 -->
function throttle(func, wait) {
// 上一次时间
let previous = 0;
return function() {
let now = Date.now();
let context = this;
let args = arguments;
// 当前时间 - 上一次时间 大于了触发事件,则执行
if (now - previous > wait) {
func.apply(context, args);
// 执行后将时间赋值给变量
previous = now;
}
}
}
<!-- 定时器 -->
function throttle(func, wait) {
let timeout;
return function() {
let context = this;
let args = arguments;
if (!timeout) {
timeout = setTimeout(() => {
timeout = null;
func.apply(context, args)
}, wait)
}
}
}
# 数组扁平化
let arr = [[1, 4, 5], 12, 24, [2, 6, [19, [35, 90]]], 10, [13, 18], [[22, 29]]]
<!-- 方法一 Array.prototype.flat -->
const result = arr.flat(Infinity);
// flat() 方法会移除数组中的空项
let arr1 = [1, 2, , 4, 5];
arr1.flat(); // [1, 2, 4, 5]
<!-- 方法二 利用正则,替换花括号 -->
const res2 = JSON.stringify(arr).replace(/\[|\]/g, '').split(',');
<!-- 方法三 改良版正则,上面得到的数据类型会变成字符串 -->
const res3 = JSON.parse('[' + JSON.stringify(arr).replace(/\[|\]/g, '') + ']');
<!-- 方法四 利用 toString & split -->
function flatten(arr) {
// toString 将数组变为字符串
// split 将字符串分割为数组
return arr.toString().split(',').map(function(item) {
return Number(item);
});
}
<!-- 方法五 利用 join & split -->
function flatten(arr) {
return arr.join(',').split(',').map(function(item) {
return Number(item);
});
}
<!-- 方法六 reduce -->
function flatten(arr) {
return arr.reduce((result, item)=> {
return result.concat(Array.isArray(item) ? flatten(item) : item);
}, []);
}
<!-- 方法七 递归 -->
function flatten(arr) {
let res = [];
arr.map(item => {
if(Array.isArray(item)) {
res = res.concat(flatten(item));
} else {
res.push(item);
}
});
return res;
}
# 数组去重
let arr = [1, 2, '1', '2', 'a', 'b', true, true, false, '3', '2', 8, 'b']
// 方法一 利用 Set ,因为 Set 中的值总是唯一的
const res1 = [...new Set(arr)];
const res2 = Array.form(new Set(arr));
// 方法二 利用 indexOf | includes
function unique(arr) {
const res = [];
for (let i = 0; i < arr.length; i++) {
// 一下二选一
if (res.indexOf(arr[i]) === -1) {
res.push(arr[i]);
}
if (!res.includes(arr[i])) {
res.push(arr[i]);
}
}
return res;
}
// 方法三 利用 filter + indexOf
function unique(arr) {
return arr.filter((item, index) => {
// 在数组中的索引==当前索引值
return arr.indexOf(item) === index;
});
}
// 方法四 利用 Map
function unique(arr) {
let map = new Map();
let array = [];
for (let i = 0; i < arr.length; i++) {
// 没有则添加
if (!map.has(arr[i])) {
map.set(arr[i], true)
array.push(arr[i]);
}
}
return array;
}
# 深拷贝
<!-- 简易版 -->
function DeepCopy(target) {
// 基础类型直接返回
if (!target || (target && typeof target !== 'object' && typeof target !== 'function')) {
return target;
}
let data;
// 判断是否是 object 对象
function isObject(obj) {
let type = typeof obj;
return obj != null && type == 'object';
}
if (Array.isArray(target)) {
data = [];
for (let item of target) {
// 数组每项也可能是引用类型,对其做深拷贝
data.push(DeepCopy(item));
}
} else if (isObject(target)) {
data = {}
for (let key in target) {
data[key] = DeepCopy(target[key]);
}
} else {
data = target;
}
return data;
}
<!-- 优化版 -->
function DeepCopy(target) {
// 用 WeakMap 记录对象,防止循环引用(WeakMap的键名只能是对象,除 null 意外)
const map = new WeakMap();
// 判断是否为 object 类型
function isObject(value) {
const type = typeof value;
return value != null && (type == 'object' || type == 'function');
}
function clone(data) {
// 基础类型直接返回
if (!isObject(data)) {
return data;
}
// 日期或正则返回一个新对象
if ([Date, RegExp].includes(data.constructor)) {
return new data.constructor(data);
}
// 函数类型
if (typeof data === 'function') {
return new Function('return ' + data.toString())();
}
// 判断对象是否存在
const exist = map.get(data);
if (exist) return exist;
// 处理 Map 对象
if (data instanceof Map) {
const result = new Map();
map.set(data, result);
data.forEach((value, key) => {
// 如果值是 object 也需要深拷贝
result.set(key, isObject(value) ? clone(value) : value);
});
return result;
}
// 处理 Set 对象
if (data instanceof Set) {
const result = new Set();
map.set(data, result);
data.forEach((value, key) => {
// 如果值是 object 也需要深拷贝
result.add(key, isObject(value) ? clone(value) : value);
});
return result;
}
// 收集键名,返回这个对象自身的属性(考虑了以Symbol作为key以及不可枚举的属性)
const keys = Reflect.ownKeys(data);
// 利用 Object 的 getOwnPropertyDescriptors 方法可以获取对象的所有属性以及对应的属性描述
const objAllDesc = Object.getOwnPropertyDescriptors(data);
// 利用 Object 的 create 方法创建新对象,并继承传入原对象的原型链,则可以得到的 result 是 data 的浅拷贝
const result = Object.create(Object.getPrototypeOf(data), objAllDesc);
// 新对象加入到 map 中
map.set(data, result);
// Object.create() 是浅拷贝,所以要执行深拷贝
keys.forEach(key => {
const value = data[key];
result[key] = isObject(value) ? clone(value) : value;
});
return result;
}
return clone(target);
}