JS 手写经典题
发表于:2023-03-15
字数统计:10.3k 字
阅读时长:26 分钟
阅读量:396
call
- 语法:fn.call(obj,...args)
- 功能:执行 fn,使 this 为 obj,并将后面的 n 个参数传给 fn
Function.prototype.myCall = function (obj, ...args) {
if (obj == undefined || obj == null) {
obj = globalThis;
}
obj.fn = this;
let res = obj.fn(...args);
delete obj.fn;
return res;
};
value = 2;
let foo = {
value: 1,
};
let bar = function (name, age) {
console.log(name, age, this.value);
};
bar.myCall(foo, "HearLing", 18); //HearLing 18 1
bar.myCall(null, "HearLing", 18); //HearLing 18 2
apply
- 语法:fn.apply(obj,arr)
- 功能:执行 fn,使 this 为 obj,并 arr 数组中元素传给 fn
Function.prototype.myAplly = function (obj, arr) {
if (obj == undefined || obj == null) {
obj = globalThis;
}
obj.fn = this;
let res = obj.fn(...arr);
delete obj.fn;
return res;
};
value = 2;
let foo = {
value: 1,
};
let bar = function (name, age) {
console.log(name, age, this.value);
};
bar.myAplly(foo, ["HearLing", 18]); //HearLing 18 1
bar.myAplly(null, ["HearLing", 18]); //HearLing 18 2
bind
- 语法:fn.bind(obj,...args)
- 功能:返回一个新函数,给 fn 绑定 this 为 obj,并制定参数为后面的 n 个参数
Function.prototype.myBind = function (obj, ...args) {
let that = this;
let fn = function () {
if (this instanceof fn) {
return new that(...args);
} else {
return that.call(obj, ...args);
}
};
return fn;
};
value = 2;
let foo = {
value: 1,
};
let bar = function (name, age) {
console.log(name, age, this.value);
};
let fn = bar.myBind(foo, "HearLing", 18);
//fn() //HearLing 18 1
let a = new fn(); //HearLing 18 undefined
console.log(a.__proto__); //bar {}
防抖和节流
防抖和节流都是用来控制事件触发频率的技术,适用于需要限制函数执行频率的场景。
区别:
防抖是将多次执行变为最后一次执行。
节流是将多次执行变成每隔一段时间执行。
应用场景:
防抖:
如输入框输入、滚动页面等,可以防止函数多次执行,以达到减少函数执行次数,降低服务器压力和提高页面性能的效果。例如:
- 输入框搜索:当用户输入时,不立即搜索,而是等用户停止输入一段时间后才执行搜索操作,避免多次请求服务器;
- 按钮点击:当用户连续点击某个按钮时,只执行一次点击事件,避免重复提交表单或其他操作;
- 窗口调整:当用户调整窗口大小时,不会触发多次调整事件,而是等用户停止调整一段时间后才执行窗口调整事件,避免频繁重排和重绘页面。
节流:
节流的应用场景主要是在需要限制函数执行频率的场合,如滚动事件、鼠标移动事件等。
- 滚动事件:限制滚动事件的触发频率,避免在滚动时过多地执行处理函数。
- 鼠标移动事件:限制鼠标移动事件的触发频率,避免在鼠标移动时过多地执行处理函数。
- 窗口大小改变事件:限制窗口大小改变事件的触发频率,避免在窗口大小改变时过多地执行处理函数。
防抖
- 理解:在函数频繁触发是,在规定之间以内,只让最后一次生效
- 场景:搜索框实时联想(keyup/input)、按钮点击太快,多次请求(登录、发短信)、窗口调整(resize)
- 功能:防抖函数在被调用后,延迟 delay 毫秒后调用,没到 delay 时间,你又点了,清空计时器重新计时,直到真的等了 delay 这么多秒。
function debounce(fn, delay) {
let timer = null;
return (...args) => {
clearTimeout(timer);
timer = setTimeout(() => {
fn.apply(this, args);
}, delay);
};
}
合并(立即执行/非立即执行)
function debounce(fn, delay = 300, immediate = false) {
var timer = null;
return function () {
var params = [this, arguments];
clearTimeout(timer);
if (immediate) {
!timer && fn.apply(...params);
timer = setTimeout(() => {
timer = null;
}, delay)
} else {
timer = setTimeout(() => {
fn.apply(...params);
}, delay)
}
}
}
节流
- 理解:在函数多次频繁触发时,函数执行一次后,只有大于设定的执行周期后才会执行第二次
- 场景:页面滚动(scroll)、DOM 元素的拖拽(mousemove)、抢购点击(click)、播放事件算进度信息
- 功能:节流函数在设置的 delay 毫秒内最多执行一次(简单点说就是,我上个锁,不管你点了多少下,时间到了我才解锁)
function throttle(fn, delay) {
let flag = true;
return (...args) => {
if (!flag) return;
flag = false;
setTimeout(() => {
fn.apply(this, args);
flag = true;
}, delay);
};
}
合并版(立即执行/非立即执行)
function throttle(fn, delay = 300, immediate = true) {
var flag = true;
return function() {
if(flag) {
var func = () => { return fn.apply(this, arguments) }
immediate && func();
flag = false;
setTimeout(() => {
!immediate && func();
flag = true;
}, delay);
}
}
}
用滚动事件来做一个测试
function handleScroll(e) {
console.log(e)
}
window.addEventListener('scroll', throttle(handleScroll, 200, true), false)
函数柯里化 curry
函数柯里化(Currying)是一种将函数转换为接收一部分参数并返回另一个函数的技术。被转换后的函数能够继续接收剩余的参数并返回结果。这个过程可以被多次重复,直到所有参数都被传递完毕并返回最终结果。
function mycurry(fn, beforeRoundArg = []) {
return function () {
let args = [...beforeRoundArg, ...arguments];
if (args.length < fn.length) {
return mycurry.call(this, fn, args);
} else {
return fn.apply(this, args);
}
};
}
function sum(a, b, c) {
return a + b + c;
}
let sumFn = mycurry(sum);
console.log(sumFn(1)(2)(3)); //6
new
1、创建一个新对象,并将新对象的原型连接到构造函数的原型
2、执行构造函数,将构造函数的 this 指向新对象
3、返回新对象,如果构造函数返回非空对象,则返回该对象,否则返回新对象
function myNew(constructor, ...args) {
let obj = Object.create(constructor.prototype)
let res = constructor.apply(obj, args)
return (typeof res === 'object' && res !== null) ? res : obj
}
测试:
function Person(name, age) {
this.name = name;
this.age = age;
}
const person = myNew(Person, 'John', 30);
console.log(person.name); // 输出:John
console.log(person.age); // 输出:30
instanceof
function instance_of(left, right) {
let prototype = right.prototype;
while (true) {
if (left === null) {
return false;
} else if (left.__proto__ === prototype) {
return true;
}
left = left.__proto__;
}
}
let a = {};
console.log(instance_of(a, Object)); //true
深拷贝
// 浅拷贝的方法
//JSON.parse(JSON.stringify(obj))
function deepClone(target, hashMap = new WeakMap()) {
if (typeof target !== "object" || target == null) {
if (target instanceof Function) return target.call(this, ...arguments);
return target;
}
if (target instanceof Date) return new Date(target);
if (target instanceof RegExp) return new RegExp(target);
let res = new target.constructor();
if (hashMap.get(target)) return hashMap.get(target);
hashMap.set(res, target);
for (let key in target) {
res[key] = deepClone(deepClone(target[key], hashMap));
}
return res;
}
const a = {
i: Infinity,
s: "",
bool: false,
n: null,
u: undefined,
sym: Symbol(),
obj: {
i: Infinity,
s: "",
bool: false,
n: null,
u: undefined,
sym: Symbol(),
},
array: [
{
nan: NaN,
i: Infinity,
s: "",
bool: false,
n: null,
u: undefined,
sym: Symbol(),
},
123,
],
fn: function () {
return "fn";
},
date: new Date(),
re: /hi\d/gi,
};
let a2 = deepClone(a);
console.log(a2 !== a);
console.log(a2.i === a.i);
console.log(a2.s === a.s);
console.log(a2.bool === a.bool);
console.log(a2.n === a.n);
console.log(a2.u === a.u);
console.log(a2.sym === a.sym);
console.log(a2.obj !== a.obj);
console.log(a2.array !== a.array);
console.log(a2.array[0] !== a.array[0]);
console.log(a2.array[0].i === a.array[0].i);
console.log(a2.array[0].s === a.array[0].s);
console.log(a2.array[0].bool === a.array[0].bool);
console.log(a2.array[0].n === a.array[0].n);
console.log(a2.array[0].u === a.array[0].u);
console.log(a2.array[0].sym === a.array[0].sym);
console.log(a2.array[1] === a.array[1]);
console.log(a2.fn !== a.fn);
console.log(a2.date !== a.date);
console.log(a2.re !== a.re);
// 都要为 true
数组扁平化
// 递归展开
function flattern1(arr) {
let res = [];
arr.foreach((item) => {
if (Array.isArray(item)) {
res.push(...flattern1(item));
} else {
res.push(item);
}
});
return res;
}
数组去重
function unique(arr) {
const res = [];
const obj = {};
arr.forEach((item) => {
if (obj[item] === undefined) {
obj[item] = true;
res.push(item);
}
});
return res;
}
//其他方法
//Array.from(new Set(array))
//[...new Set(array)]
手写 reduce
// 语法 array.reduce(function(prev, currentValue, currentIndex, arr), initialValue)
Array.prototype.MyReduce = function (fn, initialValue) {
// 浅拷贝数组const arr = Array.prototype.slice.call(this);
// 注意: reduce() 对于空数组是不会执行回调函数的。if (!arr.length) return;
let res; // res(是上面的prev)// 默认初始值
res = initialValue || arr[0];
// 遍历数组的每一个值for (let i = initialValue ? 0 : 1; i < arr.length; i++) {
// 每一个值都会在该方法中被(加工处理),
res = fn.call(null, res, arr[i], i, this);
}
// 最后的返回值return res;
};
快排(算法)
function quickSort(arr) {
if (arr.length < 2) return arr;
const cur = arr[arr.length - 1];
const left = arr.filter((v, i) => v <= cur && i !== arr.length - 1);
const right = arr.filter((v) => v > cur);
return [...quickSort(left), cur, ...quickSort(right)];
}
// console.log(quickSort([3, 6, 2, 4, 1]));
大数相加(算法)
function add(a, b) {
// 取两个数字的最大长度const maxLength = Math.max(a.length, b.length);
// 用0去补齐长度
a = a.padStart(maxLength, 0); // "0009007199254740991"
b = b.padStart(maxLength, 0); // "1234567899999999999"// 定义加法过程中需要用到的变量let t = 0;
let f = 0; // "进位"let sum = "";
for (let i = maxLength - 1; i >= 0; i--) {
t = parseInt(a[i]) + parseInt(b[i]) + f;
f = Math.floor(t / 10);
sum = (t % 10) + sum;
}
if (f !== 0) sum = `${f}${sum}`;
return sum;
}
手写简易 Ajax 请求
function ajax({method = 'GET', url, params = {}}) {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest()
xhr.open(method, url, true)
xhr.onreadystatechange = function () {
if(xhr.readyState === 4) {
const status = xhr.status
if(status === 200) {
resolve(JSON.parse(xhr.responseText))
}else if(status === 404) {
reject(new Error('404 not found'))
}
}
}
xhr.send(JSON.stringify(params))
})
}
ajax({
method: 'POST',
url: '/login',
params: {
username: '张三',
password: '123456'
}
}).then(data => {
console.log(data)
}).catch(err => {
console.log(err)
})
手写简易 jQuery 考虑插件和扩展性
html
<body>
<p>第一段文字 1</p>
<p>第二段文字 2</p>
<p>第三段文字 3</p>
<script src="./jquery.js"></script>
<script>
const p = new jQuery('p');
console.log(p)
console.log(p.get(1))
p.each(el => {
console.log(el.nodeName)
})
p.on('click', () => {
alert('clicked')
})
p.log('插件')
</script>
</body>
js
class jQuery {
constructor(selector) {
const result = document.querySelectorAll(selector);
const length = result.length;
for (let i = 0; i < length; i++) {
this[i] = result[i];
}
this.length = length;
this.selector = selector;
}
get(index) {
return this[index];
}
each(fn) {
for (let i = 0; i < this.length; i++) {
const el = this[i];
fn(el)
}
}
on(type, fn) {
return this.each(el => {
el.addEventListener(type, fn, false);
})
}
}
// 插件
jQuery.prototype.log = function(info) {
console.log(info)
}
// 造轮子
class myjQuery extends jQuery {
constructor(selector) {
super(selector)
}
// 扩展自己的方法
addClass(className) {}
style(data) {}
}