开始之前,理解递归
手写 浅拷贝
js
function shallow(target){
if(target instanceof Array){
return [...resObj]
}else{
return Object.assign({},target);
}
}
手写深拷贝
js
const _sampleDeepClone = target => {
// 补全代码
return JSON.parse(JSON.stringify(target))
}
无法处理处理循环引用的问题
完整写法
js
function DeepCopy(target, map = new Map()) {
//对于普通类型 直接返回即可
if(typeof target!=='object' || target===null){return target}
//主要处理的是引用类型
let result = Array.isArray(target)?[]:{};
if (map.has(target)) {
result = map.get(target);
} else {
map.set(target, result);
for (let key in target) {
if (target.hasOwnProperty(key)) {
if (typeof target[key] === "object") {
result[key] = DeepCopy(target[key], map);
} else {
result[key] = target[key];
}
}
}
}
return result;
}
手写new
当你使用 new Person() 来调用构造函数 Person 时,它会创建一个新的对象,并将该对象的原型设置为 Person.prototype。这个新对象可以通过 this 关键字在构造函数内部访问和设置属性。
js
function Person(name, age){
this.name=name;
this.age=age;
return{
abc:'abc'
}
//mynew中最后的判断就是为了防止这种情况:构造函数会返回值,而不会创建新的实例
}
function myNew(func, ...args) {
//接受不确定长度的参数放到args数组中
if (typeof func !== "function") {
return new TypeError("fn must be a function");
}
//把新对象的__proto__指向func.prototype
let obj = Object.create(func.prototype);
//构造函数内部的 this 值指向新创建的对象,从而在构造函数中正确地初始化新对象的属性和方法。
//apply会立即执行
//result接受执行结果
let result = func.apply(obj, args);
//如果构造函数有返回值,则返回这个返回值,没有的话返回新创建的对象
if (result && (typeof result === "object" || typeof result === "function")) {
return result;
} else {
return obj;
}
}
// const f1=new Person("xiaoming",22)
const f2= myNew(Person,"xiaoming",22)
console.log(f2);
手写instanceof
js
function myInstanceof(obj,target){
//obj是被判断的对象
//target是一个构造函数
if(typeof obj!=='object'|| typeof target !== 'function'){
return new TypeError("inputs must be object type!")
}
let objProto = obj.__proto__
while(objProto!==null){
//注意这里用的是 构造函数.prototype
if(objProto===target.prototype) return true
objProto = obj.__proto__
}
return false
}
console.log(myInstanceof([1,2],Array));
Object.create
Object.create(proto)返回一个新对象,新对象的原型是proto
【可以不看】proto应该填入一个对象,而不是一个构造函数。
如果你使用 Object.create(Person),而不是 Object.create(Person.prototype)
person 对象的原型将直接设置为 Person 构造函数本身,而不是 Person 构造函数的原型。这意味着 person 对象将无法继承 Person.prototype 上的属性和方法。
这意味着 person 对象将继承 Function.prototype 上的属性和方法,而不是 Person.prototype 上的属性和方法。
js
function myOBJcreate(proto){
function f(){}
f.prototype = proto
return new f()
}
const obj1={
name:'alice',
age:11
}
const f1 = Object.create(obj1)
const f2 = myOBJcreate(obj1)
//不需要比较,因为f1 f2是两个不同的对象
手写 防抖
主要思路:定时器控制1s后触发,并且保证只有一个定时器会被触发(timer的作用)
其实就是用定时器,来控制,这个函数只能1s后触发。
js
let Inp = document.querySelector("input");
function getValue() {
console.log(`获取${Inp.value}`);
}
Inp.addEventListener("keyup", debounce(getValue, 1000));
//业务代码
function debounce(func, time) {
let timer = null;
return function () {
if (!timer) {
timer = setTimeout(() => {
func.apply(this, arguments);
timer = null;
}, time);
}
};
}
如果你想传参:
js
let Inp = document.querySelector("input");
function getValue(name) {
console.log(`${name}获取${Inp.value}`);
}
newfunc = debounce(getValue, 1000);
Inp.addEventListener("keyup", ()=>{
newfunc("xiaoming")
});
function debounce(func, time) {
let timer = null;
return function () {
if (!timer) {
timer = setTimeout(() => {
func.apply(this, arguments);
timer = null;
}, time);
}
};
}
节流防抖巧记!
下面是业务代码,只有一行不同。
可以先把防抖的写出来(防抖的更容易理解),然后吧func.apply移出timer即可。
即节流第一次触发就要执行。
js
function debounce(func, delay) {
let timer = null;
return function () {
if (!timer) {
timer = setTimeout(() => {
func.apply(this, arguments);
timer = null;
}, delay);
}
};
}
const throttle = (func, delay) => {
let timer;
return function () {
if (!timer) {
func.apply(this, arguments);
timer = setTimeout(() => {
timer = null;
}, delay);
}
};
};
手写 节流(另一种方式,了解)
主要思路:判断上次触发的时间和这次的时间
使用方法是这样的:
js
const throttledFunction = throttle(myFunction, 1000); // 设置节流时间为1秒
throttledFunction就是一个具有节流功能的函数。你可以调用它来代替原始的myFunction函数。
js
const throttle = (func, delay) => {
let timer;
return function () {
if (!timer) {
func.apply(this, arguments);
timer = setTimeout(() => {
timer = null;
}, delay);
}
};
};
function test(name){
this.name = name;
console.log(`name is ${name}`);
}
let newfunc = throttle(test,5000)
newfunc('xiaoming') //只会运行1个
newfunc('xiaoming')
手写call
函数科里化的实现
前置:reduce方法:
arr.reduce((accumulator, currentValue, currentIndex, array)=>{},初始值)
实现add函数:
js
function add(...args){
const sum = args.reduce((total,num)=>total+num,0);
//用curry判断还有没有剩余参数
function curry(...nextArgs){
//闭包,递归的终止条件
//如果没有剩余参数,输出结果
if(nextArgs.length===0){return sum;}
//如果有,则继续加
return add(sum,...nextArgs);
}
return curry;
}
console.log(add(1)(2)(3)()); // 输出: 6
console.log(add(1, 2, 3)(4)()); // 输出: 10
console.log(add(1)(2)(3)(4)(5)()); // 输出: 15
promise.all
要点:
- promise里面不需要return,而是需要resolve和reject
- 如果在Promise.all中的Promise数组中有一个或多个Promise被拒绝(即失败),Promise.all返回的新的Promise会立即被拒绝,并且会传递第一个被拒绝的Promise的错误原因。
思路:for循环遍历所有的promises数组,所有的都成功则resolve,有一个失败则立即reject
js
function myPromiseAll(promises) {
return new Promise((resolve, reject) => {
if (!Array.isArray(promises)) {
reject(new TypeError("参数必须是一个数组"));
}
let results = [];
if (promises.length === 0) {
resolve(results);
}
for (let promise of promises) {
promise
.then((res) => {
results[results.length] = res;
if (results.length === promises.length) {
return resolve(results);
}
})
.catch((error) => {
reject(error);
});
}
});
}
Promise.race
要点:
如果在 Promise.race 中的第一个 Promise 对象被拒绝(rejected),则整个 Promise.race 会立即拒绝(reject)并返回该拒绝的原因。后续的 Promise 对象不会再被执行。
js
function myPromiseRace(promises) {
return new Promise((resolve, reject) => {
if (!Array.isArray(promises)) {
reject(new TypeError("argus must be a array"));
}
for (let promise of promises) {
promise
.then((result) => {
resolve(result);
})
.catch((error) => {
reject(error);
});
}
});
}
数组扁平化
方法1:展开数组,层层剥开
【推荐】
js
let arr = [1, [2, [3, 4, 5]]]
function flatten2(arr){
while(arr.some((item)=>Array.isArray(item))){
arr = [].concat(...arr)
}
return arr;
}
console.log(flatten2(arr));
方法2:判断当前项是否为数组 如果是数组递归调用 不是就push到新数组
js
function flatten(arr) {
let newArr=[];
for(let item of arr){
if(Array.isArray(item)){
newArr = newArr.concat(flatten(item))
}else{
newArr.push(item)
}
}
return newArr;
}
数组去重
两种方法
js
function only1(arr){
return arr.filter((value,index)=>{return arr.indexOf(value)===index})
}
其中,indexOf 方法会返回指定元素在数组中第一次出现的索引
js
function only(arr){
return [...new Set(arr)]
}
实现reduce
array.reduce(callbackfn: (previousValue,currentValue, currentIndex, array), initialValue )
js
Array.prototype.myReduce = function(callback, initialValue){
//判断参数是否正确
if(typeof callback !=='function'){return new TypeError("callback must be a function")}
let accumulator;
//检查有没有设置初始值
if(!initialValue){
if(this.length===0){return new TypeError("can't reduce a empty array")}
accumulator = this[0]
}else{
accumulator = initialValue
}
//循环调用callback
for(let i = initialValue?0:1;i<this.length;i++){
accumulator = callback(accumulator,this[i],i,this)
}
//返回结果值
return accumulator;
}
console.log([1,2,3].myReduce((pre,cur)=>{return pre+cur},0));
实现push
js
Array.prototype.myPush = function(){
for(let i=0;i<arguments.length;i++){
this[this.length]=arguments[i]
}
return this.length;
}
数组转树
测试数据:
js
let source = [
{
id: 1,
pid: 0,
name: 'body',
},
{
id: 2,
pid: 1,
name: 'title',
},
{
id: 3,
pid: 2,
name: 'div',
},
{
id: 4,
pid: 0,
name: 'html',
},
{
id: 5,
pid: 4,
name: 'div',
},
{
id: 6,
pid: 5,
name: 'span',
},
{
id: 7,
pid: 5,
name: 'img',
},
][
// 转为
({
id: 1,
pid: 0,
name: 'body',
children: [
{
id: 2,
pid: 1,
name: 'title',
children: [{ id: 3, pid: 2, name: 'div' }],
},
],
},
{
id: 4,
pid: 0,
name: 'html',
children: [
{
id: 5,
pid: 4,
name: 'div',
children: [{ id: 7, pid: 5, name: 'img' }],
},
],
})
]
普通版本:
js
function buildTree(arr, parentId) {
let result = [];
for (const item of arr) {
if (item.pid === parentId) {
item.children = buildTree(arr, item.id);
result.push(item);
}
}
return result;
}
可以防止循环引用的版本:
js
function buildTree(arr, pid = 0) {
const nodeMap = new Map();
const result = [];
arr.forEach((item) => nodeMap.set(item.id, { ...item, children: [] }));
for (const item of arr) {
if (item.pid === pid) {
const children = buildTree(arr, item.id);
nodeMap.get(item.id).children = children;
result.push(nodeMap.get(item.id));
}
}
return result;
}
树转数组
js
function TreeToArray(arr){
let result = []
for(let item of arr){
if(item.children){
result = result.concat(TreeToArray(item.children))
delete item.children
result.push(item)
}else{
result.push(item)
}
}
return result;
}
斐波那契数列的迭代和递归实现
js
function fiber(n) {
if (n >= 0) {
if (n == 0) return 0;
if (n == 1) return 1;
return fiber(n - 1) + fiber(n - 2);
}
}
function fiber2(n){
let F=[]
F[0]=0;
F[1]=1;
for(let i=2;i<=n;i++){
F[i]=F[i-1]+F[i-2]
}
console.log(F);
return F[n]
}
实现每隔一秒打印 1,2,3,4
注意那个时间一定要写对!!1000*i
js
for(let i=1;i<5;i++){
setTimeout(()=>{
console.log(i)
},1000*i)
}
使用 setTimeout 实现 setInterval
这个问题的背景:
setInterval有时会失效
,比如下面这个例子:
这样的话,每次调用这个回调之间的间隔就会超过1s,因为后面一个回调需要等待前面一个回调完成。
运行:
正常情况下:
不正常情况下:
//基础版
js
function newInterval(fn, delay) {
function inside(){
fn()
setTimeout(inside,delay);
}
inside();
}
上面的代码有个缺点就是第一个 inside();执行的fn不会被放入任务队列,而是放入了执行栈,这与setinerval的行为不符,为了更加模拟,可以这样修改:
js
function newInterval(func, delay){
function inside(){
func()
setTimeout(inside,delay)
}
setTimeout(inside,delay);
}
完整版:加上取消定时器的功能:
js
function newInterval(func,delay){
let timer;
function inside(){
func();
timer = setTimeout(inside,delay)
}
timer = setTimeout(inside,delay)
return{
cancel:function(){
clearTimeout(timer);
}
}
}