Call
Function.prototype.tcCall = function (innerthis, ...args) {
console.log(this, innerthis, args);
//获取被调用的函数foo,也就是this
let fn = this;
//获取绑定的对象,判断类型
let callThis =
innerthis !== null || innerthis !== undefined ? Object(innerthis) : window;
//调用函数
callThis.fn = fn;
let result = callThis.fn(...args);
delete callThis.fn;
return result;
};
function foo(...args) {
console.log(this.name + '是我');
console.log(args + '参数是我');
}
let testfoo = foo.tcCall({ name: 'thunder' }, 1, 2, 3);
console.log(testfoo);
Apply
Function.prototype.tcApply = function (applythis, args) {
let fn = this;
let innerapplythis =
applythis !== null && applythis !== undefined ? Object(applythis) : window;
//调用函数
innerapplythis.fn = fn;
let result = null;
//判断是否传参
if (!args) {
result = innerapplythis.fn();
} else {
result = innerapplythis.fn(...args);
}
delete innerapplythis.fn;
return result;
};
function foo(arg1, arg2) {
console.log(this.age + '岁');
console.log(arg1, arg2 + '参数是我');
}
let testfoo = foo.tcApply({ age: '18' }, [1, 2]);
console.log(testfoo);
Bind
Function.prototype.tcbind = function (bindthis, ...outArgs) {
let fn = this;
let innerBindThis =
bindthis !== null && bindthis !== undefined ? bindthis : window;
return function (...innerArgs) {
innerBindThis.fn = fn;
let newArr = outArgs.concat(innerArgs);
let result = innerBindThis.fn(newArr);
delete innerArgs.fn;
return result;
};
};
function foo(args) {
console.log(this.name, ...args);
}
foo.tcbind({ name: 'thunder' }, 123)(123);
Promise
const PROMISE_STATUS_PENDING = 'pending';
const PROMISE_STATUS_FULFILLED = 'fulfilled';
const PROMISE_STATUS_REJECTED = 'rejected';
function execFunctionWithCatchError(execFn, value, resolve, reject) {
try {
const result = execFn(value);
resolve(result);
} catch (error) {
reject(error);
}
}
class TCPromise {
constructor(executor) {
console.log(executor);
this.status = PROMISE_STATUS_PENDING;
//保存回调值
this.value = undefined;
this.reason = undefined;
//调用的所有回调
this.onfullfilledFns = [];
this.onrejectedFns = [];
const resolve = (value) => {
if (this.status === PROMISE_STATUS_PENDING) {
queueMicrotask(() => {
//防止初始化时resolve 和 reject 同时调用
if (this.status !== PROMISE_STATUS_PENDING) return;
this.status = PROMISE_STATUS_FULFILLED;
this.value = value;
this.onfullfilledFns.forEach((fn) => {
fn(this.value);
});
});
}
};
const reject = (reason) => {
if (this.status === PROMISE_STATUS_PENDING) {
//添加微任务
queueMicrotask(() => {
if (this.status !== PROMISE_STATUS_PENDING) return;
this.status = PROMISE_STATUS_REJECTED;
this.reason = reason;
this.onrejectedFns.forEach((fn) => {
fn(this.reason);
});
});
}
};
try {
// 执行函数
executor(resolve, reject);
} catch (error) {
reject(error);
}
}
then(onfullfilled, onrejected) {
const defineOnRejected = (err) => {
throw err;
};
onrejected = onrejected || defineOnRejected;
const defalutOnFulfilled = (value) => {
return value;
};
onfullfilled = onfullfilled || defalutOnFulfilled;
//链式调用
return new TCPromise((resolve, reject) => {
//如果在then函数调用的时候,状态已经确定下来,
if (this.status === PROMISE_STATUS_FULFILLED && onfullfilled) {
execFunctionWithCatchError(onfullfilled, this.value, resolve, reject);
}
if (this.status === PROMISE_STATUS_REJECTED && onrejected) {
execFunctionWithCatchError(onrejected, this.reason, resolve, reject);
}
//将成功回调和失败回调放入到数组中
if (this.status === PROMISE_STATUS_PENDING) {
if (onfullfilled) {
this.onfullfilledFns.push(() => {
execFunctionWithCatchError(
onfullfilled,
this.value,
resolve,
reject,
);
});
}
if (onrejected) {
this.onrejectedFns.push(() => {
execFunctionWithCatchError(
onrejected,
this.reason,
resolve,
reject,
);
});
}
}
});
}
catch(onrejected) {
return this.then(undefined, onrejected);
}
finally(onfinally) {
this.then(
() => {
onfinally();
},
() => {
onfinally();
},
);
}
static resolve(value) {
return new TCPromise((resolve) => resolve(value));
}
static reject(reason) {
return new TCPromise((resovle, reject) => reject(reason));
}
static all(promises) {
return new TCPromise((resolve, reject) => {
const values = [];
promises.forEach((promise) => {
promise.then(
(res) => {
values.push(res);
if (values.length === promises.length) {
resolve(values);
}
},
(err) => {
reject(err);
},
);
});
});
}
static allSettled(promises) {
return new TCPromise((resolve) => {
const results = [];
promises.forEach((promise) => {
promise.then(
(res) => {
results.push({ status: PROMISE_STATUS_FULFILLED, value: res });
if (results.length === promises.length) {
resolve(results);
}
},
(err) => {
results.push({ status: PROMISE_STATUS_REJECTED, value: err });
if (results.length === promises.length) {
resovle(results);
}
},
);
});
});
}
static race(promises) {
return new TCPromise((resolve, reject) => {
promises.forEach((promise) => {
promise.then(
(res) => {
resolve(res);
},
(err) => {
reject(err);
},
);
});
});
}
static any(promises) {
const reasons = [];
return new TCPromise((resolve, reject) => {
promises.forEach(
(promise) => {
promise.then(resolve);
},
(err) => {
reasons.push(err);
if (reasons.length === promises.length) {
reject(new AggregateError(reasons));
}
},
);
});
}
}
Vue响应式原理
class TCVue {
//获取options.data
constructor() {
this.$options = options;
this.$data = options.data;
this.$el = options.el;
//数据响应式Observer
//每个data属性对应这一个Dep对象
//初次的时候Dep.target 为 null
new Observer(this.$data);
//处理el
//此时
this.$mount(this.$el);
}
$mount(el) {
//添加订阅者
const updateView = (_) => {
let innerHtml = document.querySelector(el).innerHtml;
let key = innerHtml.match(/{(\w+)}/)[1];
document.querySelector(el).innerHtml = this.options.data[key];
};
new Watcher(updateView, true);
}
}
class Observer {
constructor(data) {
//遍历data
if (
!data ||
!Object.prototype.toString.call(data).indexOf('Object') ||
Object.getOwnPropertyDescriptor(obj).configurable === false
) {
throw new Error('data错误');
}
if (Array.isArray(data)) {
Object.setPrototypeOf(data,arrayProto)
} else {
this.walk(data);
}
}
walk(obj) {
const keys = Object.keys(obj);
for (let i = 0; i < keys.length; i++) {
//defineReactive :真实的数据响应式
defineReactive(obj, keys[i]);
}
}
}
const defineReactive = function (obj, val) {
//判断obj是否符合存取属性描述
//添加响应之前添加dep依赖, 因为每个keys[i]会被多个watcher使用
const dep = new Dep();
Object.defineProperty(obj, val, {
configurable: true,
enumerbale: true,
get() {
if (Dep.target) {
dep.depend(Dep.target);
}
return val;
},
set(nv) {
if (nv === val) {
return;
}
val = nv;
dep.notify()
},
});
};
//依赖
let uuid = 0;
class Dep {
constructor() {
this.id = uuid++;
this.subs = [];
}
//添加依赖
depend(sub) {
this.subs.push(sub);
}
//派发更新
notify() {
this.subs.forEach((sub) => {
sub.update();
});
}
}
Dep.target = null;
//watcher 订阅数据变化,绑定更新函数
//挂载的时候,get函数触发,解析el中的模板中的指令
//此时 getter函数,会触发$data中属性的get操作,
//将订阅者Watcher添加到Dep中
class Watcher {
constructor(expOrFn, isRenderWatcher) {
this.getter = expOrFn;
//更新状态
this.get();
}
get() {
//每个
Dep.target = this;
this.getter();
Dep.target = null;
}
update() {
this.get();
}
}
//数组
const arrayProto = Array.prototype
const arrayMethods = Object.create(arrayProto)
const methodsToPatch = [
'push',
'pop',
'shift',
'unshift',
'splice',
'sort',
'reverse'
]
//重写原型方法
methodsToPatch.forEach(item => {
switch (method) {
case 'push':
arrayMethods['push'].apply(this.arguments)
break;
default:
break;
}
//派发更新
const ob = this.__ob__
ob.dep.notify()
})
同时也可以参考Vue响应式原理
深拷贝
function isObject(objtype) {
let obj = typeof objtype;
return obj !== null && obj === 'object';
}
function deepClone(val, map = new WeakMap()) {
//对特殊类型进行判断
//map set
if (val instanceof Map) {
return new Map([...val]);
}
if (val instanceof Set) {
return new Set([...val]);
}
//判断是否是symbol
if (typeof val === 'symobl') {
return Symbol(val.description);
}
//判断是否是函数
if (typeof val === 'function') {
return val;
}
//判断val 是否是对象 , 如果是基本类型,除了symbol外,会直接作为值进行返回。
if (!isObject(val)) {
console.log(val);
return val;
}
//判断是否重复
if (map.has(val)) {
return map.get(val);
}
//判断传入的对象是否是数组还是对象
let newObj = Array.isArray(val) ? [] : {};
// 保留deep
map.set(val, newObj);
//深拷贝
for (const key in val) {
if (Object.hasOwnProperty.call(val, key)) {
const el = val[key];
newObj[key] = deepClone(el, map);
}
}
//因为symbol 遍历不会出现在for...in、for...of循环中
let symbolKeys = Object.getOwnPropertySymbols(val);
for (const key of symbolKeys) {
newObj[key] = deepClone(val[key], map);
}
return newObj;
}
防抖节流
防抖
//防抖
function debounce(fn, delay, immediate = false) {
let timer = null;
let isInvoke = false;
const _debounce = function (...args) {
if (timer) {
clearTimeout(timer);
}
if (immediate && !isInvoke) {
fn.call(this, args);
isInvoke = true;
} else {
timer = setTimeout(() => {
fn.apply(this, args);
isInvoke = false;
timer = null;
}, delay);
}
};
//添加取消功能
_debounce.cancel = function () {
if (timer) {
clearTimeout(timer);
isInvoke = false;
timer = null;
}
};
return _debounce
}
节流
function throttle(
fn,
interval,
options = {
//头部执行,还是尾部执行
leading: true, //头部
trailing: false, //尾部
},
) {
const { leading, trailing } = options;
//保留上次触发的时间
let lastTime = 0;
let timer = null;
const _throttle = function (...args) {
//获取当前时间
let currentTime = new Date().getTime();
if (!leading && !lastTime) {
//默认的时间
lastTime = currentTime;
}
//保留的时间
//第一次为负数,肯定是立即执行的
let remainTime = interval - (currentTime - lastTime);
if (remainTime <= 0) {
if (timer) {
clearTimeout(timer);
timer = null;
}
fn.apply(this, args);
lastTime = currentTime;
return;
}
if (trailing && !timer) {
timer = setTimeout(() => {
timer = null;
lastTime = !leading ? 0 : new Date().getTime();
fn();
}, remainTime);
}
};
return _throttle;
}
diff算法(Vue2 / Vue3)
Vue2双端Diff
function patchChildren(n1, n2, container) {
if (typeof n2.children === 'string') {
//省略代码
} else if (Array.isArray(n2.children)) {
//封装函数 patchKeyedArray 函数处理两组子节点
} else {
//省略代码
}
}
function patchKeyedChildren(n1, n2, container) {
const oldChildren = n1.children;
const newChildren = n2.children;
//四个索引值
let oldStartIdx = 0;
let oldEndIdx = oldChildren.length - 1;
let newStartIdx = 0;
let newEndIdx = newChildren.length - 1;
//四个索引值对应的vnode节点
let oldStartVnode = oldChildren[oldStartIdx];
let oldEndVnode = oldChildren[oldEndIdx];
let newStartVnode = newChildren[newStartIdx];
let newEndVnode = newChildren[newEndIdx];
while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {
//增加两个判断分支,如果头尾部为undefined, 则说明该节点被处理过,直接跳到下一个位置
if (!oldStartVnode) {
oldStartVnode = oldChildren[++oldStartIdx];
} else if (!oldEndVnode) {
oldEndVnode = newChildren[--oldEndVnode];
} else if (oldStartVnode.key === newStartVnode.key) {
//第一步
//只需要补丁
patch(oldStartVnode, newStartVnode, container);
oldStartVnode = oldChildren[++oldStartIdx];
newStartVnode = newChildren[++newStartIdx];
} else if (oldStartVnode.key === newEndVnode.key) {
//第二步
//调用 patch函数 在oldstartVnode 和 newStartVnode 之间打补丁
patch(oldStartVnode, newEndVnode, container);
insert(oldStartVnode.el, container, oldEndVnode.el.nextSibling);
oldStartVnode = oldChildren[++oldStartIdx];
newEndVnode = newChildren[--newEndIdx];
} else if (oldEndVnode.key === newEndVnode.key) {
//第三步
//节点在新的顺序中仍然处于尾部,不需要移动,但需要打补丁
patch(oldEndVnode, newEndVnode, container);
//更新索引和头尾节点变量
oldEndVnode = oldChildren[--oldEndIdx];
newEndVnode = newChildren[--newEndIdx];
} else if (oldEndVnode.key === newStartVnode.key) {
//第四步
//首先需要调用patch函数进行补丁
patch(oldEndVnode, newStartVnode, container);
//移动DOM操作
// oldEndVnode.el 移动到oldStartVnode 前面
insert(oldEndVnode.el, container, oldStartVnode);
oldEndVnode = oldChildren[--oldEndIdx];
newStartVnode = newChildren[++newStartIdx];
} else {
//遍历旧的一组子节点,视图寻找于newStartVnode拥有相同Key值的节点
//idxInOld 就是新的一组子节点的头部节点在旧的一组节点中的索引
const idxInOld = oldChildren.findIndex(
(node) => node.key === newStartVnode.key,
);
//如果idxInOld大于0 说明已经找到了可以复用的节点, 并且需要将其对应的DOM移动到头部
if (indxInOld > 0) {
const vnodeToMove = oldChildren[idxInOld];
//不要忘记移除移动操作外还应该打补丁
patch(vnodeToMove, newStartVnode, container);
//将vnodetoMove。el 移动到头部节点oldStartVnode.el之前 因此需要后者作为描点
insert(vnodeToMove.el, container, oldStartVnode.el);
oldChildren[idxInOld] = undefined;
// //更新 newStartIdx到下一个位置
// newStartVnode = newChildren[++newStartIdx]
} else {
//添加元素
// 将newStartVnode作为新的节点挂载到头部,使用当前头部节点oldStartVnode.el作为描点
patch(null, newStartVnode, container, oldStartVnode.el);
}
//更新 newStartIdx到下一个位置
newStartVnode = newChildren[++newStartIdx];
}
}
//添加新节点
if (oldEndIdx < oldStartIdx && newStartIdx <= newEndIdx) {
//如果满足条件说明有新增的节点遗漏,需要挂载他们
for (let i = newStartIdx; i < newEndIdx; i++) {
patch(null, newChildren[i], container, oldStartVnode.el);
}
} else if (newEndIdx < newStartIdx && oldStartIdx <= oldEndIdx) {
//删除操作
for (let i = oldStartIdx; i < oldEndIdx; i++) {
unmount(oldChildren[i]);
}
}
}
Vue3快速diff
function patchKeyedChildren(n1, n2, container) {
const newChildren = n2.children;
const oldChildren = n1.children;
//处理相同的前置节点
//索引 j 指向新旧两组子节点的开头
let j = 0;
let oldVnode = oldChildren[j];
let newVnode = newChildren[j];
//while循环向后遍历, 直到遇到拥有不同key值的节点为止
while (oldVnode.key === newVnode.key) {
//调用patch进行更新
patch(oldVnode, newVnode, container);
//更新J ,让其递增
j++;
oldVnode = oldChildren[j];
newVnode = newChildren[j];
}
//更新相同的后置节点
let oldEnd = oldChildren.length - 1;
let newEnd = newChildren.length - 1;
oldVnode = oldChildren[oldEnd];
newVnode = newChildren[newEnd];
//while 循环从后往前遍历, 直到遇到不同key值为止
while (oldVnode.key === newVnode.key) {
//调用patch进行更新
patch(oldVnode, newVnode, container);
//递减
oldEnd--;
newEnd--;
oldVnode = oldChildren[oldEnd];
newVnode = newChildren[newEnd];
}
//预处理完毕之后, 如果满足如下条件, 则说明 j --> nextEnd之间的节点应作为新节点插入
if (j > oldEnd && j <= newEnd) {
//描点的索引
const anchorIndex = newEnd + 1;
const anchor =
anchorIndex < newChildren.length ? newChildren[anchorIndex].el : null;
//采用 while循环,调用patch函数 逐个挂载新增节点
while (j <= newEnd) {
patch(null, newChildren[j++], container, anchor);
}
} else if (j > newEnd && j <= oldEnd) {
// j => oldEnd之间的节点应该被卸载
while (j <= oldEnd) {
unmount(oldChildren[j++]);
}
} else {
//处理 非理想情况
// 构建 source 数组
// 新的一组子节点中剩余未处理节点的数量
const count = newEnd - j + 1;
const source = new Array(count);
source.fill(-1);
//oldStart newStart 分别为其实索引,即 j
const oldStart = j;
const newStart = j;
//新增两个变量
let moved = false;
let pos = 0; // 遍历过程中遇到的最大索引值key
// 构建索引表
const keyIndex = {};
for (let i = newStart; i <= newEnd; i++) {
keyIndex[newChildren[i].key] = i;
}
//新增 patched 变量,代表更新过的节点数量
let patched = 0;
//遍历旧的子节点
for (let i = oldStart; i <= oldEnd; i++) {
const oldVnode = oldChildren[i];
if (patched <= count) {
//通过索引表快速找到新的一组子节点具有相同key值的节点位置
const k = keyIndex[oldVnode.key];
if (typeof k !== 'undefined') {
const newVnode = newChildren[k];
patch(oldVnode, newVnode, container);
//没更新一个节点 都将patched 变量 + 1
patched++;
source[k - newStart] = i;
//判断节点是否需要移动
if (k < pos) {
moved = true;
} else {
pos = k;
}
} else {
//没找到
unmount(oldVnode);
}
} else {
//如果更新过的节点数量 大于需要更新的节点数量 则卸载多余的节点
unmount(oldVnode);
}
}
if (moved) {
//如果moved 为true 则需要移动dom
const seq = lis(source);
//S 指向 最长递归子序列的最后一个元素
let s = seq.length - 1;
//i 指向新的一个子节点的最后一个元素
let i = count - 1;
//for 循环使得I递减,
for (i; i >= 0; i--) {
if (source[i] === -1) {
//说明索引为I的节点为全新的节点, 应该将其挂载
//获取该节点在新的children中的真实位置索引
const pos = i + newStart;
const newVnode = newChildren[pos];
//该节点的下一个节点的位置索引
const nextPos = pos + 1;
//描点
const anchor =
nextPos < newChildren.length ? newChildren[nextPos].el : null;
//挂载
putch(null, newVnode, container, anchor);
} else if (i !== seq[s]) {
//如果节点的索引 i,不等于seq[s] 的值,说明该节点需要移动
// 获取新的节点的真实位置
const pos = i+newStart
const newVnode = newChildren[pos]
//该节点的下一个节点的位置索引
const nextPos = pos + 1
//描点
const anchor = nextPos < newChildren.length?newChildren[nextPos].el : null
// 移动
insert(newVnode.el,container,anchor)
} else {
//如果节点的索引 i,等于seq[s] 的值,说明该节点不需要移动
//只需要让S指向下一个指针
s--;
}
}
}
}
}
封装一个axios吧
import axios from 'axios'
class MyRequest {
constructor(options) {
this.config = options
this.interceptorHooks = options.interceptorHooks
this.showLoading = options.showLoading
this.instance = axios.create(options)
this.setUpInterceptor()
}
//设置拦截器
setUpInterceptor() {
this.instance.interceptors.request.use(
this.interceptorHooks.requestInterceptor,
this.interceptorHooks.requestInterceptorCatch
)
this.instance.interceptors.response.use(
this.interceptorHooks.responseInterceptor,
this.interceptorHooks.responseInterceptorCatch
)
this.instance.interceptors.request.use((config) => {
return config
})
this.instance.interceptors.response.use(
(res) => {
return res
},
(err) => {
return err
}
)
}
//设置request
request(config) {
return new Promise((resolve, reject) => {
this.instance
.request(config)
.then((res) => {
resolve(res)
})
.catch((err) => {
reject(err)
})
})
}
get(config) {
return this.request({ ...config, method: 'get' })
}
post(config) {
return this.request({ ...config, method: 'post' })
}
}
const itcRequest = new MyRequest({
baseURL: '',
timeout: 1000,
interceptorHooks: {
requestInterceptor: (config) => {
const token = localStorage.getCache('token')
return config
},
requestInterceptorCatch: (err) => {
return err
},
responseInterceptor: (res) => {
return res.data
},
responseInterceptorCatch: (err) => {
return err
}
}
})
export function getAmountList() {
return itcRequest.get({})
}
树级菜单递归
var data = [
{ id: 1, name: '用户管理', pid: 0, child: null },
{ id: 2, name: '菜单申请', pid: 1, child: null },
{ id: 3, name: '信息申请', pid: 1, child: null },
{ id: 4, name: '模块记录', pid: 2, child: null },
{ id: 5, name: '系统设置', pid: 0, child: null },
{ id: 6, name: '权限管理', pid: 5, child: null },
{ id: 7, name: '用户角色', pid: 6, child: null },
{ id: 8, name: '菜单设置', pid: 6, child: null },
];
function arrayToTree (items) {
const result = []; // 存放结果集
const itemMap = {}; //
for (let i = 0; i < items.length; i++) {
const item = items[i];
let id = item.id;
let pid = item.pid;
//首先以id为键值添加到itemMap中
if (!itemMap[id]) {
itemMap[id] = {
child: []
}
}
itemMap[id] = {
...item,
child: itemMap[id]['child']
}
//将itemMap[id]添加到 result
const treeItem = itemMap[id];
if (pid === 0) {
result.push(treeItem);
} else {
if (!itemMap[pid]) {
itemMap[pid] = {
child: [],
};
}
//根据对象的特性,修改itemMap会直接修改result中的元素
itemMap[pid].child.push(treeItem);
}
}
return result;
}
//递归实现
function arrayToTree(data, parentId = 0) {
const tree = [];
data.forEach(item => {
if (item.pid === parentId) {
const children = arrayToTree(data, item.id);
if (children.length) {
item.children = children;
}
tree.push(item);
}
});
return tree;
}
let at = arrayToTree(data);
console.log(at);