1. 手写防抖函数
函数防抖是抖是指在事件被触发 n 秒后再执⾏回调,如果在这 n 秒内事件⼜被触发,则重新计时。这可以使⽤在⼀些点击请求的事件上,避免因为⽤户的多次点击向后端发送多次请求。
场景:
- 搜索框搜索输入,只需用户最后一次输入完,在发送请求。
- 手机号、邮箱验证输入检测。
- 窗口大小的resize,只需要窗口调整完整后再计算窗口大小。
function debounce(func, delay) {
let timer = null
return function() {
const context = this
const args = [...arguments]
// 取消之前的定时器,重新计时
clearTimeout(timer)
// 设置定时器,使时间间隔指定事件后执行
timer = setTimeout(function() {
func.apply(context,args)
},delay)
}
}
加入immediate,表示立即执行。
function debounce(func, delay) {
let timer = null
return function() {
let context = this
let args = [...arguments]
if (timer) {
clearTimeout(timer)
}
// 如果立即执行
if (immediate) {
// 第一次会立即执行 以后只有事件执行后才会再次触发
let callNow = !timer
timer = setTimeout(function() {
timer = null
},delay)
if (callNow) {
func.apply(context,args)
}
}
timer = setTimeout(function() {
func.apply(context,args)
}
,delay)
}
2. 手写节流函数
n 秒后在执行该事件,若在 n 秒内被重复触发,则重新计时。
场景:
- 滚动加载,加载更多或滚到底部监听
- 搜索框,搜索联想功能
function throttle(fn, delay) {
let timer = null;
return function () {
if (!timer) {
timer = setTimeout(() => {
fn.apply(this, arguments);
timer = null;
}, delay);
}
}
}
使用定时器写法,delay毫秒后第一次执行,第二次事件停止触发后依然会再一次执行,
可以将时间戳写法的特性与定时器写法的特性相结合,实现一个更加精确的节流。实现如下
function throttle(fn, delay) {
let timer = null
let startTime = Date.now()
return function () {
let currentTime = Date.now()
let remaining = delay - (currentTime - startTime)
if (remaining <= 0) {
fn.apply(this, arguments)
startTime = Date.now()
} else {
if (!timer) {
timer = setTimeout(() => {
fn.apply(this, arguments);
timer = null;
}, remaining);
}
}
}
}
3. 手写bind
/**
* call 、apply 、bind 作用是改变函数执行时的上下文,简而言之就是改变函数运行时的this指向
* bind()方法返回的是一个函数
*
* 实现bind 的步骤 可以分解成三个部分
* 1.修改this指向
* 2.动态传递参数
* // 方式一:只在bind中传递函数参数
* fn.bind(obj,1,2)()
* // 方式二:在bind中传递函数参数,也在返回函数中传递参数
* fn.bind(obj,1)(2)
*/
Function.prototype.myBind = function (context) {
if (typeof this !== 'function') {
throw new TypeError('Error')
}
// this指向当前对象
var _this = this
// 这里slice(1)是获取参数 因为第一个参数往往是obj,所以不考虑
var args = [...arguments].slice(1)
// 返回⼀个函数
return function F() {
// 因为返回了⼀个函数,我们可以 new F(),所以需要判断
if (this instanceof F) {
// 根据调用方式,传入不同绑定值
return new _this(...args, ...arguments)
}
return _this.apply(context, args.concat(...arguments))
}
}
4. 手写call
/**
* 1.call()方法是函数原型对象的方法,调用者为函数对象
* 可以将一个对象指定为第一个参数,此时这个对象就会成为函数执行时候的this,从第二个函数依次传入实参
* call(obj, a, b);第一个参数表示函数中的this,a,b表示实参
*
*2. apply()可以将一个对象指定为第一个参数,此时这个对象将会成为函数执行时的this,
* 实参需要封装到一个数组中,统一传递
* apply(obj, [a, b]);第一个参数表示函数中的this,[a,b]数组表示实参数组
*
* 3.bind()一个参数表示函数中的this,a,b表示实参,需要单独用括号
* bind(obj)(a,b)
*/
// 注意只有在函数执行时候才会存在this
//1.实现call()
function add(a, b) {
// console.log(this)
return a + b + this.c;
}
var c = 20;
const obj = {
c: 20,
};
//改变this指向的函数,对象,剩余参数
function call(fn, obj, ...args) {
//给obj临时绑定方法
obj.temp = fn;
//执行方法 这里就是解构参数 称为扩展运算符
const result = obj.temp(...args);
//删除方法
delete obj.temp;
return result;
}
console.log(call(add, obj, 20, 20));
//伪代码
Function.prototype.myCall = function (context, ...arr) {
if (context === null || context === undefined) {
// 指定为 null 和 undefined 的 this 值会自动指向全局对象(浏览器中为window)
context = window;
} else {
context = Object(context); // 值为原始值(数字,字符串,布尔值)的 this 会指向该原始值的实例对象
}
const specialPrototype = Symbol('特殊属性Symbol'); // 用于临时储存函数
context[specialPrototype] = this; // 函数的this指向隐式绑定到context上
let result = context[specialPrototype](...arr); // 通过隐式绑定执行函数并传递参数
delete context[specialPrototype]; // 删除上下文对象的属性
return result; // 返回函数执行结果
};
5. 手写apply
Function.prototype.apply = function(context = window, args) {
if (typeof this !== 'function') {
throw new TypeError('Type Error');
}
const fn = Symbol('fn');
context[fn] = this;
const res = context[fn](...args);
delete context[fn];
return res;
}
6. 手写instanceof
instanceof运算符用于检测构造函数的prototype属性是否出现在某个实例对象的原型链上。
function instanceOf(left, right) {
// 获取对象的原型
// let proto = Object.getPrototypeOf(left)
let leftValue = left.__proto__;
let rightValue = right.prototype;
while (true) {
if (leftValue === null) return false;
if (leftValue === rightValue) return true;
leftValue = leftValue.__proto__;
}
}
const arr = new Array();
console.log(instanceOf(arr, Array)); //true
7. 手写new
在JavaScript中,new操作符用于创建一个给定构造函数的实例对象,主要过程:
- 创建一个新的对象
obj - 将对象与构建函数通过原型链连接起来
- 将构建函数中的
this绑定到新建的对象obj上 - 根据构建函数返回类型作判断,如果是原始值则被忽略,如果是返回对象,需要正常处理
function mynew(Func, ...args) {
// 1.创建一个新对象
const obj = {};
// 2.新对象原型指向构造函数原型对象
obj.__proto__ = Func.prototype;
// 3.将构建函数的this指向新对象
let result = Func.apply(obj, args);
// 4.根据返回值判断
return result instanceof Object ? result : obj;
}
8. 函数柯理化
柯里化是什么:是指这样一个函数,它接收函数 A,并且能返回一个新的函数,这个新的函数能够处理函数 A 的剩余参数。
/**
* 指的是将一个接受多个参数的函数 变为接收一个参数返回一个函数的固定形式
*
add(1,2,3)
add(1)(2)(3)
add(1)(2,3)(4)
*/
// 1.方法一
function add() {
//将传入的不定参数转换为数组对象
//在ES6以前,我们将一个伪数组转换为真正数组通常情况下都是使用[ ].slice.call()方法
// es6方法是Array.from
// 将slice方法指向arguments arguments调用slice方法 复制一份数组
// var _args = Array.prototype.slice.call(arguments)
var args = [...arguments]
var adder = function () {
args.push(...arguments)
return adder //返回的是函数
}
adder.toString = function () {
return args.reduce(function (a, b) {
return a + b
}, 0)
}
return adder
}
let a = add(1, 2, 3).toString()
let b = add(1)(2)(3).toString()
let c = add(1)(2, 3)(4).toString()
console.log(a) // 6
console.log(a === 6) // true
console.log(c) // 10
9. 扁平化数组
/**
* 数组扁平化 并去除其中重复的数据 最终得到一个升序且不重复的数组
*
*/
var arr = [
[1,2,2],
[3,4,5,5],
[6,7,8,9, [11,12,[12,12,[13]]]],
10
]
Array.prototype.flat = function () {
const result = this.map( function (item) {
// 判断元素是否是数组
if (Array.isArray(item)) {
//是的话进行递归
return item.flat()
}
// 可以用数组包裹一下
return[item]
})
// 将每一轮的递归调用的结果进行连接
return [].concat(...result)
}
// console.log(arr.flat())
// 方法二:使用while循环
Array.prototype.flat2 = function () {
//结果的初始值就是我们数组本身
let result = this
//寻找数组中有数组元素的值
while(result.some(function (item) {
return Array.isArray(item)
})) {
//如果存在item本身是一个数组的话 就进行扁平化 ...result
result = [].concat(...result)
}
return result
}
// 去重 加排序
console.log([...new Set(arr.flat2())].sort( (a,b) => {
return a - b
}))
10. 数组转树
扁平数据结构
let arr = [
{id: 1, name: '部门1', pid: 0},
{id: 2, name: '部门2', pid: 1},
{id: 3, name: '部门3', pid: 1},
{id: 4, name: '部门4', pid: 3},
{id: 5, name: '部门5', pid: 4},
]
要求输出结果
[
{
"id": 1,
"name": "部门1",
"pid": 0,
"children": [
{
"id": 2,
"name": "部门2",
"pid": 1,
"children": []
},
{
"id": 3,
"name": "部门3",
"pid": 1,
"children": [
// 结果 ,,,
]
}
]
}
]
let arr = [
{id: 1, name: '部门1', pid: 0},
{id: 2, name: '部门2', pid: 1},
{id: 3, name: '部门3', pid: 1},
{id: 4, name: '部门4', pid: 3},
{id: 5, name: '部门5', pid: 4},
]
// function arraytoTree(items) {
// const result = []; // 存放结果集
// const itemMap = {}; //
// for (const item of items) {
// const id = item.id;
// const pid = item.pid;
// if (!itemMap[id]) { //{id: 1, name: '部门1', pid: 0, children: []}
// itemMap[id] = {
// children: [],
// }
// }
// itemMap[id] = {
// ...item,
// children: itemMap[id]['children']
// }
// const treeItem = itemMap[id];
// if (pid === 0) {
// result.push(treeItem);
// } else {
// if (!itemMap[pid]) {
// itemMap[pid] = {
// children: [],
// }
// }
// itemMap[pid].children.push(treeItem)
// }
// }
// return result;
// }
//
let res = arraytoTree(arr, 0)
console.log(res)
/**
* 方法二
*/
function arraytoTree(arr) {
const newArr = []
const map = {}
arr.forEach((item) => {
if(!item.children) item.children = []
map[item.id] = item
})
arr.forEach((item) => {
if(map[item.pid]) {
map[item.pid].children.push(item)
}else{
newArr.push(item)
}
});
return newArr
}
11. 数组去重
const arr = [1, 1, '1', 17, true, true, false, false, 'true', 'a', {}, {}]; // => [1, '1', 17, true, false, 'true', 'a', {}, {}]
- 方法一:利用Set
1const res1 = Array.from(new Set(arr));
- 方法二:两层for循环+splice
const unique1 = arr => {
let len = arr.length;
for (let i = 0; i < len; i++) {
for (let j = i + 1; j < len; j++) {
if (arr[i] === arr[j]) {
arr.splice(j, 1);
// 每删除一个树,j--保证j的值经过自加后不变。同时,len--,减少循环次数提升性能
len--;
j--;
}
}
}
return arr;
}
- 方法三:利用indexOf
const unique2 = arr => {
const res = [];
for (let i = 0; i < arr.length; i++) {
if (res.indexOf(arr[i]) === -1) res.push(arr[i]);
}
return res;
}
当然也可以用include、filter,思路大同小异。
- 方法四:利用include
const unique3 = arr => {
const res = [];
for (let i = 0; i < arr.length; i++) {
if (!res.includes(arr[i])) res.push(arr[i]);
}
return res;
}
- 方法五:利用filter
const unique4 = arr => {
return arr.filter((item, index) => {
return arr.indexOf(item) === index;
});
}
- 方法六:利用Map
const unique5 = arr => {
const map = new Map();
const res = [];
for (let i = 0; i < arr.length; i++) {
if (!map.has(arr[i])) {
map.set(arr[i], true)
res.push(arr[i]);
}
}
return res;
}