一、防抖
function debounce(fn, delay=200) {
let timeout = null; // 定时器控制
return function(...args) {
if (timeout) { // 定时器存在,表示某个动作之前触发过了
clearTimeout(timeout); // 清除定时器
timeout = null;
} else {
// 对第一次输入立即执行
fn.apply(this, args);
}
timeout = setTimeout(()=>{
fn.apply(this, args); // this指向function
}, delay)
}
}
二、节流
function throttle(fn, time) {
let pre = 0;
let timeout = null;
return function (...args) {
const now = Date.now();
// 如果时间超过了时间间隔,立即执行函数
if (now - pre > time) {
pre = now;
fn.apply(this, args);
} else {
// 如果时间没有超过时间间隔,取消后续的定时器任务
if (timeout) {
clearTimeout(timeout);
timeout = null;
}
// 最后一次事件的触发
timeout = setTimeout(() => {
pre = now;
fn.apply(this, args);
}, time);
}
};
}
三、instanceOf
判断数据类型的方法:typeof、 instanceOf、 Object.prototype.tostring.call()
作用:运算符,可以判断一个对象的类型
原理:原型和原型链
function myInstanceOf(obj, constructor) {
// obj=实例对象,constructor=实例对象的构造函数
let proto = obj.__proto__;
while (true) { // 遍历原型链
if (proto === null) {
return false;
}
if (proto === constructor.prototype) {
return true;
}
proto = proto.__proto__;
}
}
四、call
思路:1、判断是否是函数调用,若非函数调用抛异常;2、通过新对象(context)来调用函数:给context创建一个fn
设置为需要调用的函数,结束调用完之后删除fn
// 初级写法
Function.prototype.myCall = function (ctx, ...args) {
// ctx=上下文
// 将方法挂载到传入的上下文ctx
ctx.fn = this;
// 将挂载以后的方法调用
ctx.fn(...args);
// 将添加的属性删除
delete ctx.fn;
};
// 完整写法
Function.prototype.myCall = function (obj) {
// obj是this的指向,后面还可以有多个参数即函数本身的参数
var obj = obj || window;
obj.p = this; // 为形参定义一个方法p,并把this给这个函数
var newArguments = []; // 函数参数本身是没有this的,需要把所有参数另外保存起来,并且不保留第一个this参数,使参数回归到正常的序号
// 获取函数的参数需要用到arguments对象
for (var i = 1; i < arguments.length; i++) {
newArguments.push("arguments[" + i + "]"); // 用字符串拼接参数
}
var result = eval("obj.p(" + newArguments + ")"); // 用eval可以执行到引号里的参数
delete obj.p; // 要将p方法删除掉,因为不能改写对象
return result;
};
五、apply
思路:思路:1、判断是否是函数调用,若非函数调用抛异常;2、通过新对象(context)来调用函数:给context创建一个fn
设置为需要调用的函数,结束调用完之后删除fn
Function.prototype.myApply = function (obj, arr) {
var obj = obj || window;
obj.p = this;
if (!arr) {
// 如果执行myApply的时候没有输入参数arr,那么就直接执行方法p,不用考虑参数问题
obj.p();
} else {
// 有传入参数arr,执行就跟call一样了
var newArguments = [];
for (var i = 1; i < arguments.length; i++) {
newArguments.push("arguments[" + i + "]");
}
var result = eval("obj.p(" + newArguments + ")");
}
delete obj.p;
return result;
};
六、bind
思路:1、判断是否是函数调用,若非函数调用抛异常;2、返回函数:判断函数的调用方式,是否是被new出来的,new出来的话返回空对象,但是实例的__proto__
指向_this
的prototype;3、
函数柯里化Array.prototype.slice.call()
Function.prototype.myBind = function (obj) {
if (typeof this !== "function") {
throw new TypeError("wrong");
}
var that = this;
var arr = Array.prototype.slice.call(arguments, 1);
var o = function () {};
newf = function () {
var arr2 = Array.prototype.slice.call(arguments);
var arr = arr.concat(arr2);
if (this instanceof o) {
that.apply(this, arrSum);
} else {
that.apply(obj, arrSum);
}
};
o.prototype = that.prototype;
newf.prototype = new o();
return newf;
};
七、对象深拷贝
ES5写法
ES6写法
八、手写new
new发生的事:
- 创建一个新对象
- 使新对象的__proto__指向原函数的prototype
- 改变this指向(指向新的obj)并执行该函数,执行结果保存起来作为result
- 判断执行函数的结果是不是null或Undefined,如果是则返回之前的新对象,如果不是则返回result
// 手写一个new
function myNew(fn, ...args) {
// 创建一个空对象
let obj = {}
// 使空对象的隐式原型指向原函数的显式原型
obj.__proto__ = fn.prototype
// this指向obj
let result = fn.apply(obj, args)
// 返回
return result instanceof Object ? result : obj
}
手写一个发布订阅
手写数组转树
function transTree(data) {
let result = []
let map = {}
if (!Array.isArray(data)) {//验证data是不是数组类型
return []
}
data.forEach(item => {//建立每个数组元素id和该对象的关系
map[item.id] = item //这里可以理解为浅拷贝,共享引用
})
data.forEach(item => {
let parent = map[item.parentId] //找到data中每一项item的爸爸
if (parent) {//说明元素有爸爸,把元素放在爸爸的children下面
(parent.children || (parent.children = [])).push(item)
} else {//说明元素没有爸爸,是根节点,把节点push到最终结果中
result.push(item) //item是对象的引用
}
})
return result //数组里的对象和data是共享的
}
console.log(JSON.stringify(transTree(data)))