前端手写代码题

184 阅读5分钟

1.手写Object.create

思路:将传入的对象作为原型

function create(obj) {
  function F() {}
  F.prototype = obj
  return new F()
}

2.手写instanceof方法

思路:instanceof运算符用于判断构造函数的prototype属性是否出现在对象的原型链中任何位置。

实现步骤:

  • 1.首先获取类型的原型
  • 2.然后获取对象的原型
  • 3.然后一直循环判断对象的原型是否等于类型的原型,直到对象原型为null,因为原型链最终为null
//  手写 instanceof 方法
function instanceOf(source, target) {
    let proto = Object.getPrototypeOf(source);
    while (proto) {
        if (proto === target.prototype) {
            return true;
        }
        proto = Object.getPrototypeOf(proto);
    }
    return false;
}

class Parent {
    constructor(name) {
        this.name = name;
    }
}

class Child extends Parent{

}
const temp = new Child();
console.log(instanceOf(temp, Parent));

3.手写new操作符

思路: 在调用 new 的过程中会发生以上四件事情:

(1)首先创建了一个新的空对象

(2)设置原型,将对象的原型设置为函数的 prototype 对象。

(3)让函数的 this 指向这个对象,执行构造函数的代码(为这个新对象添加属性)

(4)判断函数的返回值类型,如果是值类型,返回创建的对象。如果是引用类型,就返回这个引用类型的对象。

// 手写new操作符
function myNew(constructor) {
    if (typeof constructor !== "function") {
        throw TypeError("type error");
    }
    // 创建空对象
    const obj = {};
    // 空对象__proto__ 指向 构造函数的prototype
    obj.__proto__ = constructor.prototype;
    // 执行构造函数,并将构造函数的this指向新创建的空对象
    const returnObj = constructor.apply(obj, Array.from(arguments).slice(1))
    // 若构造函数自身有返回对象或函数,则返回构造函数返回的,否则返回新创建的对象。
    return typeof (returnObj === 'object' || typeof returnObj === "function") ? returnObj : obj;
}

4.手写Promise函数

// MyPromise.js

// 先定义三个常量表示状态
const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';

// 新建 MyPromise 类
class MyPromise {
    constructor(executor){
        // executor 是一个执行器,进入会立即执行
        // 并传入resolve和reject方法
        executor(this.resolve, this.reject)
    }

    // 储存状态的变量,初始值是 pending
    status = PENDING;
    
    // 用来存放then后面的回调函数。
    onFulfilledCallback = [];
    //用来存放catch后面的回调函数
    onRejectedCallback = [];

    // resolve和reject为什么要用箭头函数?
    // 如果直接调用的话,普通函数this指向的是window或者undefined
    // 用箭头函数就可以让this指向当前实例对象
    // 成功之后的值
    value = null;
    // 失败之后的原因
    reason = null;

    // 更改成功后的状态
    resolve = (value) => {
        // 只有状态是等待,才执行状态修改
        if (this.status === PENDING) {
            // 状态修改为成功
            this.status = FULFILLED;
            // 保存成功之后的值
            this.value = value;
            // 如果是异步函数,resolve可能在then之后执行,故如果是异步的函数,就在resolve里面调用。
            if (this.onFulfilledCallback.length !== 0) {
                this.onFulfilledCallback.shift()(value);
            }
        }
    }

    // 更改失败后的状态
    reject = (reason) => {
        // 只有状态是等待,才执行状态修改
        if (this.status === PENDING) {
            // 状态成功为失败
            this.status = REJECTED;
            // 保存失败后的原因
            this.reason = reason;
            this.onRejectedCallback && this.onRejectedCallback(reason);
            if (this.onRejectedCallback.length !== 0) {
                this.onRejectedCallback.shift()(reason);
            }
        }
    }

    then(onFulfilled, onRejected) {
        // 判断状态
        if (this.status === FULFILLED) {
            // 调用成功回调,并且把值返回
            onFulfilled(this.value);
        } else if (this.status === REJECTED) {
            // 调用失败回调,并且把原因返回
            onRejected(this.reason);
        } else if (this.status === PENDING) {
            this.onFulfilledCallback.push(onFulfilled);
            this.onRejectedCallback.push(onRejected);
        }
    }
}


const promise = new MyPromise((resolve, reject)=>{
    setTimeout(()=> {
        resolve("3秒后执行成功")
    }, 3000)
})
promise.then((data) => {
    console.log(data);
})

5.手写Promise.all函数

核心思路

  1. 接收一个 Promise 实例的数组或具有 Iterator 接口的对象作为参数
  2. 这个方法返回一个新的 promise 对象,
  3. 遍历传入的参数,用Promise.resolve()将参数"包一层",使其变成一个promise对象
  4. 参数所有回调成功才是成功,返回值数组与参数顺序一致
  5. 参数数组其中一个失败,则触发失败状态,第一个触发失败的 Promise 错误信息作为 Promise.all 的错误信息。
// Promise.all实现
function PromiseAll(promises) {
    return new Promise((resolve, reject) => {
        if (!Array.isArray(promises)) {
            throw TypeError("type error")
        }
        const result = [];
        let count = 0;
        promises.forEach(promise => {
            promise.then(val => {
                count++;
                result.push(val);
                if (count === promises.length) {
                    resolve(result);
                }
            }).catch(error => {
                reject(error);
            })
        });
    })
}

const promise1 = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve("延迟3秒")
    }, 3000);
})

const promise2 = new Promise((resolve, reject) => {
    setTimeout(() => {
        reject("延迟5秒")
    }, 5000);
})

const promise3 = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve("延迟4秒")
    }, 4000);
})

const result = PromiseAll([promise1, promise2, promise3]);
result.then(val => {
    console.log(val)
}).catch(error => {
    console.log(error)
})

6.手写Promise.race()函数

思路:谁先执行完,就返回谁。

// Promise.race实现
function PromiseRace(promises) {
    return new Promise((resolve, reject) => {
        if (!Array.isArray(promises)) {
            throw TypeError("type error")
        }
        promises.forEach(promise => {
            promise.then(val => {
               resolve(val);
            }).catch(error => {
               reject(error);
            })
        });
    })
}

7.手写一个Promise.finally?

Promise.prototype.myFinally = function (cb) {
     return this.then((value) => {
         return Promise.resolve(cb());
     }, (error) => {
         return Promise.resolve(cb());
     })
 }

promise2.myFinally(() => {
    console.log("finally正在执行")
})

8.手写一个promise.allSettled

function PromiseAllSettled(promises) {
    return new Promise((resolve, reject) => {
        if (!Array.isArray(promises)) {
            throw TypeError("type error");
        }
        const result = [];
        let count = 0;
        promises.forEach(promise => {
            promise.then(val => {
                result.push(val);
            }).catch(error => {
                result.push(error);
            }).finally(() => {
                count++;
                if (count === promises.length) {
                    resolve(result);
                }
            })
        })
    })
}

9.手写一下Promise.any?

原理:Promise.any是一个JavaScript的全局函数,它接收一个Promise对象的可迭代参数(如数组),并返回一个新的Promise对象。这个新的Promise对象将在可迭代参数中的任意一个Promise对象变为fulfilled状态时被解析,如果可迭代参数中的所有Promise对象都变为rejected状态,则返回一个rejected状态的Promise对象。

Promise.prototype.myAny = function (promises) {
     return new Promise((resolve, reject) => {
         if (Object.prototype.toString.call(promises) !== '[object Array]') {
             throw new TypeError("promises必须为数组");
         }
         let count = 0;
         promises.forEach((promise, index) => {
             promise.then((data) => {
                 resolve(data);
             }).catch((error) => {
                 count++;
                 if (count === promises.length) {
                     reject(new AggregateError('All promises were rejected'))
                 }
             })
         })
     })
 }

 const promise1 = new Promise((resolve, reject) => {
     setTimeout(() => {
         resolve('promise1');
     }, 1000)
 })
 const promise2 = new Promise((resolve, reject) => {
     setTimeout(() => {
         resolve('promise2');
     }, 3000)
 })
 const promise3 = new Promise((resolve, reject) => {
     setTimeout(() => {
         resolve('promise3');
     }, 2000)
 })

Promise.prototype.myAny([promise1, promise2, promise3]).then((data) => {
    console.log(data)
});

10.手写一个Promise.resolve()?

Promise.myResolve = function (value) {
    if (value instanceof Promise) {
        return value;
    }
    return new Promise((resolve) => resolve(value))
}
Promise.myResolve("执行成功").then(data => {
    console.log(data)
})

11.手写一个Promise.reject()?

Promise.myReject = function (value) {
    if (value instanceof Promise) {
        return value;
    }
    return new Promise((resolve, reject) => reject(value))
}
Promise.myReject("执行失败").catch(data => {
    console.log(data)
})

12.来说一下如何串行执行多个Promise?

const delay = (time) => {
    return new Promise((resolve, reject) => {
        console.log(`wait ${time}s`)
        setTimeout(() => {
            console.log("executor");
            resolve();
        }, time * 1000);
    });
}
const arr = [2, 3, 5];
// 方法一:使用reduce实现
arr.reduce((s, v) => {
    console.log(s, v)
    return s.then(() => delay(v))
}, Promise.resolve())

// 方法二:使用 async和await和循环
(async function() {
    for (let time of arr) {
        await delay(time);
    }
})()


var p = Promise.resolve();
// 方法三:用一个for循环以及闭包保存
for (const time of arr) {
    p = p.then(() => delay(time))
}

7.手写防抖函数?

原理: 函数防抖是指在事件被触发 n 秒后再执行回调,如果在这 n 秒内事件又被触发,则重新计时。这可以使用在一些点击请求的事件上,避免因为用户的多次点击向后端发送多次请求。

<body>
 <button onclick="fn()">Click</button>
</body>
// 手写防抖函数
function debounce(func, wait) {
  let timeout;

  return function(...args) {
    const context = this;  // 保留调用的上下文

    // 清除上一个定时器
    clearTimeout(timeout);

    // 设置新的定时器
    timeout = setTimeout(() => {
      func.apply(context, args);  // 调用原函数
    }, wait);
  };
}

// 使用示例
const fn = debounce(function() {
  console.log('Function executed!');
}, 1000);

// 当你快速多次调用fn时,它只会在最后一次调用后的1秒后执行。

8.手写节流函数?

原理:函数节流是指规定一个单位时间,在这个单位时间内,只能有一次触发事件的回调函数执行,如果在同一个单位时间内某事件被触发多次,只有一次能生效。节流可以使用在 scroll 函数的事件监听上,通过事件节流来降低事件调用的频率。

function throttle(callback, wait) {
    let timeout = null;
    return function () {
        const thisArg = this;
        if (!timeout) {
            timeout = setTimeout(() => {
                callback.apply(thisArg, arguments);
                clearTimeout(timeout);
                timeout = null;
            }, wait)
        }
    }
}

9.手写函数判断某个类型?

function getType(value) {
     if (value === null) {
         return value + '';
     }
     if (typeof value === "object") {
         let valueType = Object.prototype.toString.call(value);
         const type = valueType.split(" ")[1].split("");
         type.pop(); // 将最后的“]”弹出来
         return type.join("").toLowerCase();
     } else {
         return typeof value;
     }
}

10.手写call函数

call 函数的实现步骤:

  1. 判断调用对象是否为函数,即使我们是定义在函数的原型上的,但是可能出现使用 call 等方式调用的情况。
  2. 判断传入上下文对象是否存在,如果不存在,则设置为 window 。
  3. 处理传入的参数,截取第一个参数后的所有参数。
  4. 将函数作为上下文对象的一个属性。
  5. 使用上下文对象来调用这个方法,并保存返回结果。
  6. 删除刚才新增的属性。
  7. 返回结果。
// call函数实现
Function.prototype.myCall = function(context) {
  // 判断调用对象
  if (typeof this !== "function") {
    console.error("type error");
  }
  // 获取参数
  let args = [...arguments].slice(1),
  result = null;
  // 判断 context 是否传入,如果未传入则设置为 window
  context = context || window;
  // 将调用函数设为对象的方法
  context.fn = this;
  // 调用函数
  result = context.fn(...args);
  // 将属性删除
  delete context.fn;
  return result;
};

11.手写Apply函数

apply 函数的实现步骤:

  1. 判断调用对象是否为函数,即使我们是定义在函数的原型上的,但是可能出现使用 call 等方式调用的情况。
  2. 判断传入上下文对象是否存在,如果不存在,则设置为 window 。
  3. 将函数作为上下文对象的一个属性。
  4. 判断参数值是否传入
  5. 使用上下文对象来调用这个方法,并保存返回结果。
  6. 删除刚才新增的属性
  7. 返回结果
// apply 函数实现
Function.prototype.myApply = function(context) {
  // 判断调用对象是否为函数
  if (typeof this !== "function") {
    throw new TypeError("Error");
  }
  let result = null;
  // 判断 context 是否存在,如果未传入则为 window
  context = context || window;
  // 将函数设为对象的方法
  context.fn = this;
  // 调用方法
  if (arguments[1]) {
    result = context.fn(...arguments[1]);
  } else {
    result = context.fn();
  }
  // 将属性删除
  delete context.fn;
  return result;
};

12.手写bind函数

bind 函数的实现步骤:

  1. 判断调用对象是否为函数,即使我们是定义在函数的原型上的,但是可能出现使用 call 等方式调用的情况。
  2. 保存当前函数的引用,获取其余传入参数值。
  3. 创建一个函数返回
  4. 函数内部使用 apply 来绑定函数调用,需要判断函数作为构造函数的情况,这个时候需要传入当前函数的 this 给 apply 调用,其余情况都传入指定的上下文对象。

this instanceof Fn ? this : context含义:

返回一个新的函数 Fn,该函数内部通过 apply 方法来调用原函数 fn。在 apply 方法中,根据调用方式的不同决定 this 的取值:如果是通过 new 操作符调用 Fn 构造函数,则 this 会指向新创建的实例;否则,this 会指向传入的 context 上下文。

// bind 函数实现
Function.prototype.myBind = function(context, ...args) {
    if (typeof this !== 'function') {
        throw new TypeError('Bind must be called on a function');
    }

    const self = this;
    const boundFunction = function(...innerArgs) {
        // 判断是否使用new调用,如果是new调用,绑定的this值失效,而是指向新创建的对象
        if (this instanceof boundFunction) {
            return new self(...args, ...innerArgs);
        } else {
            return self.apply(context, args.concat(innerArgs));
        }
    };

    // 继承原始函数的原型
    function Temp() {}
    Temp.prototype = this.prototype;
    boundFunction.prototype = new Temp();

    return boundFunction;
};

// 示例
function example(name, age) {
    this.name = name;
    console.log(this.color);
    console.log(name, age);
}

const boundExample = example.myBind({ color: 'red' }, 'John');
boundExample(30);  // 输出:red, John, 30

const newBoundExample = new boundExample(30);  // 输出:undefined, John, 30
console.log(newBoundExample.name);  // 输出:John

13.手写实现原型链继承?

参考链接:juejin.cn/post/684490…

1.原型链继承

function Parent () {
    this.name = 'kevin';
}

Parent.prototype.getName = function () {
    console.log(this.name);
}

function Child () {

}

Child.prototype = new Parent();

var child1 = new Child();

console.log(child1.getName()) // kevin

2.借用构造函数(经典继承)

function Parent () {
    this.names = ['kevin', 'daisy'];
}

function Child () {
    Parent.call(this);
}

var child1 = new Child();

child1.names.push('yayu');

console.log(child1.names); // ["kevin", "daisy", "yayu"]

var child2 = new Child();

console.log(child2.names); // ["kevin", "daisy"]

优点:

1.避免了引用类型的属性被所有实例共享

2.可以在 Child 中向 Parent 传参

缺点:

方法都在构造函数中定义,每次创建实例都会创建一遍方法。

3.组合继承

原型链继承和经典继承双剑合璧。

function Parent (name) {
    this.name = name;
    this.colors = ['red', 'blue', 'green'];
}

Parent.prototype.getName = function () {
    console.log(this.name)
}

function Child (name, age) {

    Parent.call(this, name);

    this.age = age;

}

Child.prototype = new Parent();

var child1 = new Child('kevin', '18');

child1.colors.push('black');

console.log(child1.name); // kevin
console.log(child1.age); // 18
console.log(child1.colors); // ["red", "blue", "green", "black"]

var child2 = new Child('daisy', '20');

console.log(child2.name); // daisy
console.log(child2.age); // 20
console.log(child2.colors); // ["red", "blue", "green"]

优点:融合原型链继承和构造函数的优点,是 JavaScript 中最常用的继承模式。

4.原型式继承

function createObj(o) {
    function F(){}
    F.prototype = o;
    return new F();
}

就是 ES5 Object.create 的模拟实现,将传入的对象作为创建的对象的原型。

缺点:

包含引用类型的属性值始终都会共享相应的值,这点跟原型链继承一样。

5. 寄生式继承

创建一个仅用于封装继承过程的函数,该函数在内部以某种形式来做增强对象,最后返回对象。

function createObj (o) {
    var clone = object.create(o);
    clone.sayName = function () {
        console.log('hi');
    }
    return clone;
}

缺点:跟借用构造函数模式一样,每次创建对象都会创建一遍方法。

6. 寄生组合式继承

function Parent (name) {
    this.name = name;
    this.colors = ['red', 'blue', 'green'];
}

Parent.prototype.getName = function () {
    console.log(this.name)
}

function Child (name, age) {
    Parent.call(this, name);
    this.age = age;
}

// 关键的三步
var F = function () {};

F.prototype = Parent.prototype;

Child.prototype = new F();


var child1 = new Child('kevin', '18');

console.log(child1);

数据处理

1.实现日期格式化函数

dateFormat(new Date('2020-12-01'), 'yyyy/MM/dd') // 2020/12/01
dateFormat(new Date('2020-04-01'), 'yyyy/MM/dd') // 2020/04/01
dateFormat(new Date('2020-04-01'), 'yyyy年MM月dd日') // 2020年04月01日

const dateFormat = (dateInput, format)=>{
    var day = dateInput.getDate() 
    var month = dateInput.getMonth() + 1  
    var year = dateInput.getFullYear()   
    format = format.replace(/yyyy/, year)
    format = format.replace(/MM/,month)
    format = format.replace(/dd/,day)
    return format
}

2.交换a,b的值,不能用临时变量

a = a + b;
b = a - b;
a = a - b;

3. 实现数组的乱序输出?

function func(arr) {
    const copyArr = arr.slice();
    for (let i = 0; i < copyArr.length; i++) {
        const randomIndex = Math.floor(Math.random() * copyArr.length); //生成一个从0到数组长度的随机数
        [copyArr[i], copyArr[randomIndex]] = [copyArr[randomIndex], copyArr[i]]; // 交换当前遍历到的结点与随机数的位置。
        console.log(randomIndex);
    }
    console.log(copyArr)
}
const arr = [1,2,3,4,5];
func(arr);

4.实现数组元素求和?

  • arr=[1,2,3,4,5,6,7,8,9,10],求和
function func(arr) {
    return arr.reduce((pre, cur) => pre + cur, 0);
}
const arr = [1,2,3,4,4];
console.log(func(arr));
  • arr=[1,2,3,[[4,5],6],7,8,9],求和 递归实现:
function func(arr) {
    let result = 0;
    for (let i = 0; i < arr.length; i++) {
        if (Array.isArray(arr[i])) {
            result += func(arr[i]);
        } else {
            result += arr[i];
        }
    }
    return result;
}
const arr = [1,2,3,[[4,5],6]];
console.log(func(arr));

另一种方法:

function func(arr) {
    return arr.toString().split(',').reduce((pre, cur) => Number(pre) + Number(cur))
}
const arr = [1,2,3,[[4,5],6]];
console.log(func(arr));

5.实现数组的扁平化?

递归实现:


//实现数组的扁平化
function func(arr) {
    let result = [];
    for (let i = 0; i < arr.length; i++) {
        if (Array.isArray(arr[i])) {
            result = result.concat(func(arr[i]));
        } else {
            result.push(arr[i]);
        }
    }
    return result;
}
const arr = [1,2,3,[[4,5],6]];
console.log(func(arr));

使用reduce的方式:


//实现数组的扁平化
function func(arr) {
    return arr.reduce((pre, cur) => pre.concat(Array.isArray(cur) ? func(cur) : cur), []);
}
const arr = [1,2,3,[[4,5],6]];
console.log(func(arr));

使用toString和split的方式:


//实现数组的扁平化
function func(arr) {
    return arr.toString().split(",").map(val => Number(val));
}
const arr = [1,2,3,[[4,5],6]];
console.log(func(arr));

使用ES6中flat:


//实现数组的扁平化
function func(arr) {
    return arr.flat(Infinity);
}
const arr = [1,2,3,[[4,5],6]];
console.log(func(arr));

6. 实现数组去重?

// 使用ES6中Set和拓展运算符实现数组去重
function func(arr) {
    return [...new Set(arr)];
}

//使用indexOf 和filter方法去重
function func1(arr) {
    return arr.filter((val, index) => arr.indexOf(val) === index)
}

7. 实现数组的flat方法?(实现数组扁平化)

方式一:

function _flat(arr, depth) {
  if(!Array.isArray(arr) || depth <= 0) {
    return arr;
  }
  return arr.reduce((prev, cur) => {
    if (Array.isArray(cur)) {
      return prev.concat(_flat(cur, depth - 1))
    } else {
      return prev.concat(cur);
    }
  }, []);
}

方式二:

const arr = [1, 2, 3, 4, [1, 2, 3, [1, 2, 3, [1, 2, 3]]], 5, "string", { name: "弹铁蛋同学" }]
function myFlat(arr) {
    let result = [];
    for (let i = 0; i < arr.length; i++) {
        if (Array.isArray(arr[i])) {
            result = result.concat(...myFlat(arr[i])); //展开数组
        } else {
            result.push(arr[i]);
        }
    }
    return result;
}
let flat = myFlat(arr);
console.log(flat)

8.手写一下常见数组的高阶函数的模拟实现?(forEach,map,filter,every,some,find/findIndex,reduce)

答:

1.forEach模拟实现:

forEach主要用于数组的简单遍历

Array.prototype.myForEach = function (callbackFn) {
    // 判断this是否合法
    if (this === null || this === undefined) {
        throw new TypeError("Cannot read property 'myForEach' of null");
    }
    // 判断callbackFn是否合法
    if (Object.prototype.toString.call(callbackFn) !== "[object Function]") {
        throw new TypeError(callbackFn + ' is not a function')
    }
    // 取到执行方法的数组对象和传入的this对象
    var _arr = this, thisArg = arguments[1] || window;
    for (var i = 0; i < _arr.length; i++) {
        // 执行回调函数
        callbackFn.call(thisArg, _arr[i], i, _arr);
    }
}

2.map方法模拟实现:

map函数对数组的每个元素执行回调函数,并返回含有运算结果的新数组

Array.prototype.myMap = function (callbackFn) {
    if (this === null || this === undefined) {
        throw new TypeError("不能为空");
    }
    if (Object.prototype.toString.call(callbackFn) === '[Object Function]') {
        throw new TypeError("必须是方法");
    }
    const arr = this;
    const thisArgs = arguments[1] || window;
    const result = [];
    for (let i = 0; i < arr.length; i++) {
        result.push(callbackFn.call(thisArgs, arr[i], i, arr));
    }
    return result;
}

3.filter方法的模拟实现:

filter是过滤的意思,它对数组每个元素执行回调函数,返回回调函数执行结果为true的元素。

Array.prototype.myFilter = function (callbackFn) {
    if (this === null || this === undefined) {
        throw new TypeError("不能为空");
    }
    if (Object.prototype.toString.call(callbackFn) === '[Object Function]') {
        throw new TypeError("必须是方法");
    }
    const arr = this;
    const thisArgs = arguments[1] || window;
    const result = [];
    for (let i = 0; i < arr.length; i++) {
        if (callbackFn.call(thisArgs, arr[i], i, arr)) {
           result.push(arr[i]);
        }
    }
    return result;
}

4.every方法模拟实现:

every并不返回数组,返回布尔值 true/false ,数组的每个元素执行回调函数,如果执行结果全为 true ,every 返回 true,否则返回 false 。

Array.prototype.myEvery = function (callbackFn) {
    if (this === null || this === undefined) {
        throw new TypeError("不能为空");
    }
    if (Object.prototype.toString.call(callbackFn) === '[Object Function]') {
        throw new TypeError("必须是方法");
    }
    const arr = this;
    const thisArgs = arguments[1] || window;
    for (let i = 0; i < arr.length; i++) {
        if (!callbackFn.call(thisArgs, arr[i], i, arr)) {
           return false;
        }
    }
    return true;
}

5.some方法模拟实现:

some 与 every 功能类似,都是返回布尔值。只要回调函数结果有一个 truesome便返回 true,否则返回 false

Array.prototype.mySome = function (callbackFn) {
    if (this === null || this === undefined) {
        throw new TypeError("不能为空");
    }
    if (Object.prototype.toString.call(callbackFn) === '[Object Function]') {
        throw new TypeError("必须是方法");
    }
    const arr = this;
    const thisArgs = arguments[1] || window;
    for (let i = 0; i < arr.length; i++) {
        if (callbackFn.call(thisArgs, arr[i], i, arr)) {
           return true;
        }
    }
    return false;
}

6.find/findIndex方法模拟实现:

find 与 findIndex 是 ES6 新添加的数组方法,返回满足回调函数的第一个数组元素/数组元素索引。当数组中没有能满足回调函数的元素时,会分别返回 undefined和-1 。

Array.prototype.myFind = function (callbackFn) {
    if (this === null || this === undefined) {
        throw new TypeError("不能为空");
    }
    if (Object.prototype.toString.call(callbackFn) === '[Object Function]') {
        throw new TypeError("必须是方法");
    }
    const arr = this;
    const thisArgs = arguments[1] || window;
    for (let i = 0; i < arr.length; i++) {
        if (callbackFn.call(thisArgs, arr[i], i, arr)) {
           return arr[i];
        }
    }
    return undefined;
}

reduce方法模拟实现:

Array.prototype.myReduce = function (callbackFn) {
    if (this === null || this === undefined) {
        throw new TypeError("不能为空");
    }
    if (Object.prototype.toString.call(callbackFn) === '[Object Function]') {
        throw new TypeError("必须是方法");
    }
    var i = 0, arr = this, accumulator = arguments[1];
    if (accumulator === undefined) {
        if (arr.length === 0) {
            throw new Error("数组不能为空");
        }
        accumulator = arr[0];
        i++;
    }
    for (; i < arr.length; i++) {
        accumulator = callbackFn(accumulator, arr[i], i, arr);
    }
    return accumulator;
}

7.实现数组的push方法?


// 实现数组的push方法
Array.prototype.myPush = function () {
    let arr = this;
    for (let i = 0; i < arguments.length; i++) {
        arr[arr.length] = arguments[i];
    }
    return this.length;
}

8.实现数组的filter方法?

// 实现数组的filter方法
Array.prototype.myFilter = function (callback) {
    let arr = this;
    let result = [];
    for (let i = 0; i < arr.length; i++) {
        if (callback(arr[i], i, arr)) {
            result.push(arr[i]);
        }
    }
    return result;
}

9.js实现快速排序?

function sortArray(nums) {
    quickSort(0, nums.length - 1, nums);
    return nums;
}

function quickSort(start, end, arr) {
    if (start < end) {
        const mid = sort(start, end, arr);
        quickSort(start, mid - 1, arr);
        quickSort(mid + 1, end, arr);
    }
}

function sort(start, end, arr) {
    const base = arr[start];
    let left = start;
    let right = end;
    while (left !== right) {
        // 从右边开始查找小于基数的值
        while (left < right && arr[right] >= base) {
            right--;
        }
        arr[left] = arr[right]; // 将找到的值赋给arr[left]

        // 从左边开始查找大于基数的值
        while (left < right && arr[left] <= base) {
            left++;
        }
        arr[right] = arr[left]; // 将找到的值赋给arr[right]
    }
    arr[left] = base; // 将基数放回其正确的位置
    return left;
}

const arr = [2,1,3,4,5,3,3,4];
const result = sortArray(arr);
console.log(result);

image.png

10.手写实现一个深递归?

实现深拷贝有三种方法:

1. 使用递归拷贝来实现:

function deepClone(source) {
    let target;
    if (typeof source !== 'object' || source === null) {
        return target;
    }
    // 区分source是数组类型还是对象类型。
    if (Object.prototype.toString.call(source) === '[object Array]') {
        target = [];
    } else {
        target = {};
    }
    for (let key in source) {
        // 如果不是对象的话,直接赋值
        if (typeof source[key] !== 'object') {
            target[key] = source[key];
        } else {
            // 如果是对象,要递归遍历
            target[key] = deepClone(source[key]);
        }
    }
    return target;
}

2. 使用JSON序列化和反序列化来实现:

const obj1 = {
  a: 1,
  b: {
    c: 2,
    d: [3, 4],
  },
};
const obj2 = JSON.parse(JSON.stringify(obj1));
obj2.b.c = 5;
console.log(obj1.b.c); // 2
console.log(obj2.b.c); // 5

3. 使用lodash 的 cloneDeep() 方法

11.实现字符串的repeat()方法?

方法一:使用双层数组

//实现字符串的repeat方法
//输入字符串s,以及其重复的次数,输出重复的结果,例如输入abc,2,输出abcabc。
function repeat(str, num) {
    let result = [];
    for (let i = 0; i < num; i++) {
        for (let j = 0; j < str.length; j++) {
            result.push(str.charAt(j));
        }
    }
    return result.join("");
}

方法二: 这个 repeat 函数的工作原理如下:

  1. 使用 new Array(num + 1) 创建一个新数组,数组的长度是 num + 1,并且数组的内容是 undefined
  2. 使用 join(str) 将数组连接成一个字符串,str 作为连接的分隔符。

由于数组的内容是 undefined,所以在连接时,str 将重复 num 次。

下面是一个例子,说明了如何使用 repeat('abc', 3)

  1. 创建一个新数组,长度为 3 + 1 = 4,内容为 [undefined, undefined, undefined, undefined]
  2. 使用 join('abc') 将数组连接成字符串。由于数组中的每个元素都是 undefined,因此在连接时不会在元素之间插入任何内容,只会插入分隔符 'abc'。所以结果是 'abcabcabc'

总结一下,str 将重复 num 次,因为 join 方法使用 strnum + 1undefined 元素连接在一起,因此 str 本身会重复 num 次。

//实现字符串的repeat方法
//输入字符串s,以及其重复的次数,输出重复的结果,例如输入abc,2,输出abcabc。
function repeat(str, num) {
    return (new Array(num + 1).join(str));
}

12.实现字符串的reverse()方法?

方法一:

// 在字符串的原型链上添加一个方法,实现字符串的翻转:

String.prototype.myReverse = function () {
    return this.split('').reverse().join("");
}

方法二:

function reverseString(str) {
    let reversed = '';
    for (let char of str) {
        reversed = char + reversed;
    }
    return reversed;
}

方法三:

function reverseString(str) {
    return str.split('').reduce((acc, char) => char + acc, '');
}

13. 将数字每千分位用逗号隔开

方法一:使用toLocalString()方法

function format(num) {
    return num.toLocaleString(); //toLocalString() 可以将东西转换成本地习惯的方法,从而来实现千分位
}

format(12323.33)  // '12,323.33'

方法二:使用split+ reduce


// 实现将带有小数点的数,千分位分割
function format(num) {
    let str = num.toString();   // 转换为字符串
    let newStr;
    let isDecimalPoint = false;  // 判断是否是带有小数点的
    let secondPart = '';
    if (str.indexOf('.') !== -1) {
        let strs = str.split('.');    // 通过小数点去分割成两个字符串
        newStr = strs[0];  // 小数点前面的字符串
        secondPart = strs[1];  // 小数点后面的字符串
        isDecimalPoint = true;
    } else {
        newStr = str;
    }
    console.log(newStr)
    const firstPart = newStr
        .split('')
        .reverse()
        .reduce((pre, cur, index) => (index % 3 ? cur : cur + ',') + pre);
    if (isDecimalPoint)  {
        return firstPart + "." + secondPart;
    } else {
        return firstPart;
    }
}

const temp = format(12323.332423)  // '12,323.33'
console.log(temp)

14.实现非负大数相加?

其主要的思路如下:

  • 首先用字符串的方式来保存大数,这样数字在数学表示上就不会发生变化
  • 初始化res,temp来保存中间的计算结果,并将两个字符串转化为数组,以便进行每一位的加法运算
  • 将两个数组的对应的位进行相加,两个数相加的结果可能大于10,所以可能要仅为,对10进行取余操作,将结果保存在当前位
  • 判断当前位是否大于9,也就是是否会进位,若是则将temp赋值为true,因为在加法运算中,true会自动隐式转化为1,以便于下一次相加
  • 重复上述操作,直至计算结束

//实现非负大整数相加
function sumBigNumber(a, b) {
    let strA = a.split('');
    let strB = b.split('');
    let result = '';
    let temp = 0;
    console.log(strA);
    while (strA.length || strB.length) {
        temp = temp + Number(strA.pop() ?? 0) + Number(strB.pop() ?? 0);
        result = (temp % 10) + result;
        temp = temp > 9;
    }
    return result;
}

const temp =sumBigNumber("2134783276482364823", '21424354365476586779796847363')
console.log(temp)

15.实现 add(1)(2)(3)?

答: 方式一:暴力法:

function add(a) {
    return function (b) {
        return function (c) {
            return a + b + c;
        }
    }
}

let newVar = add(1)(2)(3)
console.log(newVar)

方式二:函数柯里化

/**
 * 将函数转换为其柯里化版本。
 * 
 * @param {Function} fn - 需要柯里化的函数。
 * @returns {Function} - 函数的柯里化版本。
 */
function curry(fn) {
    /**
     * 原始函数的柯里化版本。
     * 
     * @param {...*} args - 传递给柯里化函数的参数。
     * @returns {Function|*} - 如果提供了足够的参数,则返回原始函数的结果,否则返回期望其余参数的新函数。
     */
    return function curried(...args) {
        // 如果提供的参数数量大于或等于原始函数的参数数量,
        // 则使用提供的参数应用原始函数。
        if (args.length >= fn.length) {
            return fn.apply(this, args);
        } else {
            // 否则,返回一个期望其余参数的新函数。
            return function (...args2) {
                // 使用组合的参数列表调用柯里化函数。
                return curried.apply(this, args.concat(args2));
            }
        }
    }
}

16.将类对象转换为数组的方法?

let arr = {0: '51',1: 'max', 2: 5, 3: 2, length: 4};
//使用slice()
const temp = Array.prototype.slice.call(arr, 0);
// //使用splice()
const temp1 = Array.prototype.splice.call(arr, 0)
//使用concat()
const temp2 = Array.prototype.concat.apply([], arr)
//使用Array.from()
const temp3 = Array.from(arr);
console.log(temp3)

17.如何使用reduce求和?

  • arr = [1,2,3,4,5,6,7,8,9,10],求和
const arr = [1,2,3,4,5,6,7,8,9,10]
const temp = arr.reduce((pre, cur) => pre + cur, 0);
console.log(temp)
  • arr = [1,2,3,[[4,5],6],7,8,9],求和
const arr = [1,2,3,[[4,5],6],7,8,9]

function sum(arr) {
// 先拍平,然后再计算或者用toString也可以
    return arr.flat(Infinity).reduce((pre, cur) => pre + cur);
}
const temp = sum(arr);
console.log(temp)
  • arr = [{a:1, b:3}, {a:2, b:3, c:4}, {a:3}],求和
const arr =  [{a:1, b:3}, {a:2, b:3, c:4}, {a:3}]

function sum(arr) {
    return arr.reduce((pre, cur) => pre + objSum(cur), 0);
}

function objSum(obj) {
    let result = 0;
    for (const key in obj) {
        result += obj[key];
    }
    return result;
}
const temp = sum(arr);
console.log(temp)

18.将数组内对象转换为树形结构?

// 将js对象转化为树形结构
// 转换前:
source = [{
    id: 1,
    pid: 0,
    name: 'body'
}, {
    id: 2,
    pid: 1,
    name: 'title'
}, {
    id: 3,
    pid: 2,
    name: 'div'
}]
// 转换为:
tree = [{
    id: 1,
    pid: 0,
    name: 'body',
    children: [{
        id: 2,
        pid: 1,
        name: 'title',
        children: [{
            id: 3,
            pid: 1,
            name: 'div'
        }]
    }]
}]


function objToTree(data) {
    let result = [];
    // 先判断是否是一个数组,如果不是数组,直接返回
    if (!Array.isArray(data)) {
        return data;
    }
    let map = {};
    // 将所有对象都放入到map中
    data.forEach(val => map[val.id] = val);
    data.forEach(val => {
        // 找出当前遍历元素的父元素
        let parent = map[val.pid];
        if (parent) {
            // 如果父元素存在,则为父元素添加children
            (parent.children || (parent.children = [])).push(val);
        } else {
            // 不存在的话,说明是根节点
            result.push(val);
        }
    });
    return result;
}

const temp = objToTree(source);
console.log(temp)

 19.使用ES5和ES6求函数参数的和

ES5中:

function sum() {
    let sum = 0
    Array.prototype.forEach.call(arguments, function(item) {
        sum += item * 1
    })
    return sum
}

ES6中:

// ES6求函数的参数和
function sum(...args) {
    return args.reduce((pre, cur) => pre + cur, 0);
}

20.解析 URL Params 为对象?

let url = 'http://www.domain.com/?user=anonymous&id=123&id=456&id=378&id=556&city=%E5%8C%97%E4%BA%AC&enabled';
let url1 = 'hhttp://www.domain.com/'
const temp = parseParam(url)
console.log(temp)
/* 结果
{ user: 'anonymous',
  id: [ 123, 456 ], // 重复出现的 key 要组装成数组,能被转成数字的就转成数字类型
  city: '北京', // 中文需解码
  enabled: true, // 未指定值得 key 约定为 true
}
*/

function parseParam(url) {
    let paramStr;
    // 通过字符串'?'将两个数字分割开,后面部分为参数部份
    if (url.indexOf('?') !== -1) {
        paramStr = url.split('?')[1];
    } else {
        paramStr = '';
    }
    if (!paramStr) {
        return {};
    }
    // 将参数部份通过"&"分离
    const paramArr = paramStr.split("&");
    let result = {};
    paramArr.forEach((value, index, array) => {
        const keyOrValue = value.split("=");
        const oldKey = keyOrValue[0];
        const oldValue = keyOrValue[1];
        // 对value值部分进行解码
        let newValue = decodeURIComponent(oldValue ?? true);
        newValue = !isNaN(newValue) ? Number(newValue) : newValue;
        // 如果已经有该属性,则添加数组。
        if (result.hasOwnProperty(oldKey)) {
            result[oldKey] = [].concat(result[oldKey], newValue);
        } else {
            result[oldKey] = newValue;
        }
    })
    return result;
}

场景应用

1. 循环打印红黄绿

下面来看一道比较典型的问题,通过这个问题来对比几种异步编程方法:红灯 3s 亮一次,绿灯 1s 亮一次,黄灯 2s 亮一次;如何让三个灯不断交替重复亮灯?

function delay(color, wait) {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            console.log(color);
            resolve();
        }, wait);
    })
}

const color = ['红灯', '绿灯', '黄灯'];

(async function func() {
    while(true) {
        for (let i = 0; i < color.length; i++) {
            if (color[i] === '红灯') {
                await delay(color[i], 3000);
            }
            if (color[i] === '绿灯') {
                await delay(color[i], 1000);
            }
            if (color[i] === '黄灯') {
                await delay(color[i], 2000);
            }
        }
    }
})()

2. 实现每隔一秒打印 1,2,3,4?

答:

function delay(content) {
    return new Promise((resolve, reject) => {
        setTimeout(() =>{
            console.log(content);
            resolve();
        }, 1000);
    });
}

const arr = [1, 2, 3, 4];
(async function func() {
    for (let i = 0; i < arr.length; i++) {
       await delay(arr[i], arr[i]); //等1秒以后会继续
    }
})()

3.小孩报数问题

有30个小孩儿,编号从1-30,围成一圈依此报数,1、2、3 数到 3 的小孩儿退出这个圈, 然后下一个小孩 重新报数 1、2、3,问最后剩下的那个小孩儿的编号是多少?

function childNum(num, count) {
    let exitCount = 0; //离开人数
    let counter = 0; // 记录报数
    let curIndex = 0; // 当前下标
    let allPlayer = [];
    // 整一个数组,全部编号,代表学生数量,如果存的值为1,表示没有出局,如果为0,则表示出局
    for (let i = 0; i < num; i++) {
        allPlayer[i] = i + 1;
    }
    while(exitCount < num - 1) {
        if (allPlayer[curIndex] !== 0) counter++;
        if (counter === count) {
            allPlayer[curIndex] = 0;
            counter = 0;
            exitCount++;
        }
        curIndex++;
        if (curIndex === num) {
            curIndex = 0;
        }
    }
    for (let i = 0; i < num; i++) {
        if (allPlayer[i] !== 0) {
            return allPlayer[i];
        }
    }
}

const temp = childNum(30, 3)
console.log(temp)

 4.用Promise实现图片的异步加载

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>

</body>
<script>

    let imageAsync = (url) => {
       return new Promise((resolve, reject) => {
           let img = new Image();
           img.src = url;
           img.onload = () => {
               console.log("图片请求成功,此处进行通用操作")
               resolve(img);
           }
           img.onerror = () => {
               console.log("图片加载失败");
               reject("加载失败")
           }
       })
    }

    imageAsync("imgSrc/test.png").then((img) => {
        let element = img;
        console.log(img)
        element.width = 200;
        element.height = 200;
        document.body.appendChild(element)
        console.log("加载成功");
    }).catch((error) => {
        console.log(error);
    })
</script>

</html>

5.分片思想解决大数据量渲染问题

题目描述:渲染百万条结构简单的大数据时,怎么使用分片思想优化渲染?

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <ul id="container"></ul>
</body>
<script>
    let ul = document.getElementById("container");
    // 插入10万条数据
    let total = 100000;
    // 一次插入20条
    let once = 20;
    // 总页数
    let page = total / once;
    // 每条记录的索引
    let index = 0;
    // 循环加载数据
    function loop(curTotal, curIndex) {
        if (curTotal <= 0) {
            return false;
        }
        // 每页多少条
        let pageCount = Math.min(curTotal, once);
        window.requestAnimationFrame(function () {
            for (let i = 0; i < pageCount; i++) {
                let li = document.createElement('li');
                li.innerText = curIndex + i + ':' + Math.floor(Math.random() * total);
                ul.appendChild(li)
            }
            loop(curTotal - pageCount, curIndex + pageCount);
        });
    }
    loop(total, index)

</script>

</html>

6.如何实现双向绑定的功能?

<body>
<div>
    hello, world
    <input type="text" id = "model">
    <p id="word"></p>
</div>
</body>
<script type="text/javascript" src="myModule.js"></script>
<script>
    const model = document.getElementById("model");
    const word = document.getElementById("word");
    var obj = {};

    const newObj = new Proxy(obj, {
        get: function (target, key, receiver) {
            console.log("key===", key);
            return Reflect.get(target, key, receiver);
        },
        set: function (target, key, value, receiver) {
            console.log("set", target, key, value, receiver);
            if (key === "text") {
                model.value = value;
                word.innerHTML = value;
            }
            return Reflect.set(target, key, value, receiver);
        }
    });
    model.addEventListener("keyup", function (e) {
        newObj.text = e.target.value;
    })

</script>

效果:

image.png

7.输出以下代码运行结果?

// example 1
var a={}, b='123', c=123;
a[b]='b';

// c 的键名会被转换成字符串'123',这里会把 b 覆盖掉。
a[c]='c';  

// 输出 c
console.log(a[b]);
---------------------------------------------------
// example 2
var a={}, b=Symbol('123'), c=Symbol('123');  

// b 是 Symbol 类型,不需要转换。
a[b]='b';

// c 是 Symbol 类型,不需要转换。任何一个 Symbol 类型的值都是不相等的,所以不会覆盖掉 ba[c]='c';

// 输出 b
console.log(a[b]);
----------------------------------------------------
// example 3
var a={}, b={key:'123'}, c={key:'456'};  

// b 不是字符串也不是 Symbol 类型,需要转换成字符串。
// 对象类型会调用 toString 方法转换成字符串 [object Object]a[b]='b';

// c 不是字符串也不是 Symbol 类型,需要转换成字符串。
// 对象类型会调用 toString 方法转换成字符串 [object Object]。这里会把 b 覆盖掉。
a[c]='c';  

// 输出 c
console.log(a[b]);

8.如何将所有0移动到数组最后面?

说明:

  1. 必须在原数组上操作,不能拷贝额外的数组。
  2. 尽量减少操作次数。
const arr = [0, 1, 0, 3, 0, 12];

console.log(moveZero(arr));
function moveZero(arr) {
    let n = 0;
    arr.forEach((item, index) => {
        if (item === 0) {
            arr.splice(index, 1);
            n++;
        }
    })
    arr.push(...(new Array(n).fill(0)));
    return arr;
}

9.如何在input搜索中实现防抖的效果?

<div>
    <input type="text" id = "model">
</div>
</body>
<script type="text/javascript" src="myModule.js"></script>
<script>
let inputComp = document.getElementById("model");

inputComp.addEventListener("keyup", debounce(search, 3000))  // 这里要注意,防抖返回的就是一个函数

function search(val) {
    console.log("执行搜索", val)
}

function debounce(fn, delay) {
    let timer = null;
    return function() {
        if (timer) clearTimeout(timer);
        timer = setTimeout(() => {
            fn.apply(this, arguments);
            timer = null;
        }, delay);
    }
}

10. (京东、快手)算法题之「两数之和」?

给定一个整数数组和一个目标值,找出数组中和为目标值的两个数。

你可以假设每个输入只对应一种答案,且同样的元素不能被重复利用。

// 给定 nums = [2, 7, 11, 15], target = 9
//
// 因为 nums[0] + nums[1] = 2 + 7 = 9
// 所以返回 [0, 1]

function twoSum(arr, target) {
    const map = new Map();
    let result = [];
    for (let i = 0; i < arr.length; i++) {
        map.set(arr[i], true);
    }
    for (let i = 0; i < arr.length; i++) {
        if (map.has(target - arr[i]) && map.get(target - arr[i])) {
            result.push([arr[i], target - arr[i]]);
            map.set(arr[i], false);
            map.set(target - arr[i], false);
        }
    }
    return result;
}

const temp = twoSum([2, 7, 11, 15, 4, 5], 9);
console.log(temp)

11.实现发布-订阅模式?

class EventCenter{
    // 1. 定义事件容器,用来装事件数组
    handlers = {};

    // 2. 添加事件方法,参数:事件名 事件方法
    addEventListener(type, handler) {
        // 创建新数组容器
        if (!this.handlers[type]) {
            this.handlers[type] = []
        }
        // 存入事件
        this.handlers[type].push(handler)
    }

    // 3. 触发事件,参数:事件名 事件参数
    dispatchEvent(type, params) {
        // 若没有注册该事件则抛出错误
        if (!this.handlers[type]) {
            return new Error('该事件未注册')
        }
        // 触发事件
        this.handlers[type].forEach(handler => {
            handler(...params)
        })
    }

    // 4. 事件移除,参数:事件名 要删除事件,若无第二个参数则删除该事件的订阅和发布
    removeEventListener(type, handler) {
        if (!this.handlers[type]) {
            return new Error('事件无效')
        }
        if (!handler) {
            // 移除事件
            delete this.handlers[type]
        } else {
            const index = this.handlers[type].findIndex(el => el === handler)
            if (index === -1) {
                return new Error('无该绑定事件')
            }
            // 移除事件
            this.handlers[type].splice(index, 1)
            if (this.handlers[type].length === 0) {
                delete this.handlers[type]
            }
        }
    }
}


const event = new EventCenter();
const sum = (a, b ,c) => {
    console.log(a + b + c);
}
const sum_1 = (a, b) => {
    console.log(a + b);
}
const sub = (a, b) => {
    console.log(a - b);
}


event.addEventListener('sum', sum);
event.addEventListener('sum', sum_1);
event.addEventListener('sub', sub);
console.log(event.handlers);
event.dispatchEvent('sum', [1, 2, 3]);
event.removeEventListener('sum', sum)
console.log(event.handlers);

12.查找文章中出现频率最高的单词

function findMostFrequentWord(text) {
    // 使用正则表达式将文本分解为单词数组
    // \b:这是一个单词边界。它确保匹配的内容在单词的开始或结束位置。
    // 
    // \w:这匹配一个单词字符。在正则表达式中,\w 是一个简写字符类,它匹配任何单词字符,等同于 [a-zA-Z0-9_]。这意味着它会匹配任何字母、数字或下划线。
    // 
    // +:这表示前面的元素(在这里是 \w)可以出现一次或多次。因此,\w+ 会匹配一个或多个单词字符,即一个完整的单词。
    // 
    // g:这是一个"全局"标志,它表示正则表达式应该找到所有的匹配,而不是仅仅是第一个匹配。
    const words = text.toLowerCase().match(/\b\w+\b/g);

    // 使用对象来存储每个单词及其出现的次数
    const wordCounts = {};

    // 遍历单词数组,统计每个单词的出现次数
    for (let word of words) {
        wordCounts[word] = (wordCounts[word] || 0) + 1;
    }

    // 查找出现次数最多的单词
    let maxCount = 0;
    let mostFrequentWord = '';
    for (let word in wordCounts) {
        if (wordCounts[word] > maxCount) {
            maxCount = wordCounts[word];
            mostFrequentWord = word;
        }
    }

    return mostFrequentWord;
}

// 示例
const text = "This is a sample text. This text contains words. Some words appear more than once in this text.";
const result = findMostFrequentWord(text);
console.log(result); // 输出: "this"

13.封装异步的fetch,使用async await方式来使用

(async () => {
    // 定义一个HttpRequestUtil类,用于处理HTTP请求
    class HttpRequestUtil {
        /**
         * 发送GET请求
         * @param {string} url - 请求的URL
         * @returns {Promise<Object>} 返回的JSON数据
         */
        async get(url) {
            const res = await fetch(url);
            const data = await res.json();
            return data;
        }

        /**
         * 发送POST请求
         * @param {string} url - 请求的URL
         * @param {Object} data - 要发送的数据
         * @returns {Promise<Object>} 返回的JSON数据
         */
        async post(url, data) {
            const res = await fetch(url, {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json'
                },
                body: JSON.stringify(data)
            });
            const result = await res.json();
            return result;
        }

        /**
         * 发送PUT请求
         * @param {string} url - 请求的URL
         * @param {Object} data - 要发送的数据
         * @returns {Promise<Object>} 返回的JSON数据
         */
        async put(url, data) {
            const res = await fetch(url, {
                method: 'PUT',
                headers: {
                    'Content-Type': 'application/json'
                },
                body: JSON.stringify(data) // 注意:这里应该使用body而不是data
            });
            const result = await res.json();
            return result;
        }

        /**
         * 发送DELETE请求
         * @param {string} url - 请求的URL
         * @param {Object} data - 要发送的数据
         * @returns {Promise<Object>} 返回的JSON数据
         */
        async delete(url, data) {
            const res = await fetch(url, {
                method: 'DELETE',
                headers: {
                    'Content-Type': 'application/json'
                },
                body: JSON.stringify(data) // 注意:这里应该使用body而不是data
            });
            const result = await res.json();
            return result;
        }
    }

    // 创建HttpRequestUtil的实例
    const httpRequestUtil = new HttpRequestUtil();
    // 使用get方法发送请求,并打印返回的结果
    const res = await httpRequestUtil.get('http://golderbrother.cn/');
    console.log(res);
})();

14.实现双向数据绑定

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <style>

    </style>
</head>
<body>
<input id="input" type="text"><br>
<span id="span"></span>
</body>
<script type="text/javascript" src="myModule.js"></script>
<script>
    // 创建一个空对象用于数据存储和劫持
    let obj = {}

    // 获取页面中 id 为 'input' 和 'span' 的元素
    let input = document.getElementById('input')
    let span = document.getElementById('span')

    // 使用 Object.defineProperty 进行数据劫持
    Object.defineProperty(obj, 'text', {
        configurable: true,   // 可配置,允许重新定义属性
        enumerable: true,     // 可枚举,允许通过 for...in 循环遍历属性
        get() {
            console.log('获取数据了')   // 当访问 obj.text 时,输出获取数据的日志
        },
        set(newVal) {
            console.log('数据更新了')   // 当设置 obj.text 时,输出数据更新的日志
            input.value = newVal     // 将输入框的值设置为新值
            span.innerHTML = newVal  // 将 span 元素的 innerHTML 设置为新值
        }
    })

    // 监听输入框的键盘输入事件
    input.addEventListener('keyup', function(e) {
        obj.text = e.target.value  // 将输入框的值设置为 obj.text,触发数据劫持的 set 方法
    })


</script>
</html>

15.使用setTimeout()去实现setInterval()?

思路: setInterval 的作用是每隔一段指定时间执行一个函数,但是这个执行不是真的到了时间立即执行,它真正的作用是每隔一段时间将事件加入事件队列中去,只有当当前的执行栈为空的时候,才能去从事件队列中取出事件执行。所以可能会出现这样的情况,就是当前执行栈执行的时间很长,导致事件队列里边积累多个定时器加入的事件,当执行栈结束的时候,这些事件会依次执行,因此就不能到间隔一段时间执行的效果。

针对 setInterval 的这个缺点,我们可以使用 setTimeout 递归调用来模拟 setInterval,这样我们就确保了只有一个事件结束了,我们才会触发下一个定时器事件,这样解决了 setInterval 的问题。

实现思路是使用递归函数,不断地去执行 setTimeout 从而达到 setInterval 的效果

function mySetInterval(fn, timeout) {
    // 控制器,控制定时器是否继续执行
    var timer = {
        flag: true
    };
    // 设置递归函数,模拟定时器执行。
    function interval() {
        if (timer.flag) {
            fn();
            setTimeout(interval, timeout);
        }
    }
    // 启动定时器
    setTimeout(interval, timeout);
    // 返回控制器
    return timer;
}

let timer = mySetInterval(()=> {
    console.log("111")}, 3000)
setTimeout(() => {
    timer.flag = false;   // 可以通过这种方式将定时器终止
}, 10000)

16. 实现 jsonp

// 动态的加载js文件
function addScript(src) {
  const script = document.createElement('script');
  script.src = src;
  script.type = "text/javascript";
  document.body.appendChild(script);
}
addScript("http://xxx.xxx.com/xxx.js?callback=handleRes");
// 设置一个全局的callback函数来接收回调结果
function handleRes(res) {
  console.log(res);
}
// 接口返回的数据格式
handleRes({a: 1, b: 2});

17.判断对象是否存在循环引用

循环引用对象本来没有什么问题,但是序列化的时候就会发生问题,比如调用JSON.stringify()对该类对象进行序列化,就会报错: Converting circular structure to JSON.

下面方法可以用来判断一个对象中是否已存在循环引用:

/**
 * isCycleObject - 检查对象是否具有循环引用。
 *
 * @param {Object} obj    - 待检查的对象。
 * @param {Array} parent  - 用于追踪层级结构并检测循环的父对象数组。默认为初始对象。
 *
 * @returns {boolean}     - 如果对象包含循环引用则返回 true,否则返回 false。
 */
const isCycleObject = (obj, parent) => {
    // 以提供的对象为初始父对象开始。
    const parentArr = parent || [obj];

    // 遍历对象中的所有属性。
    for (let i in obj) {
        // 如果属性是对象,则需要进一步检查。
        if (typeof obj[i] === 'object') {
            let flag = false;

            // 检查当前对象属性是否已经在层级结构中(即在 parentArr 中)。
            // 如果是,则表示有循环引用。
            parentArr.forEach((pObj) => {
                if (pObj === obj[i]) {
                    flag = true;
                }
            });

            // 如果在此级别检测到循环,则返回 true。
            if (flag) return true;

            // 如果没有,则递归检查对象的属性以查找循环。
            flag = isCycleObject(obj[i], [...parentArr, obj[i]]);

            // 如果在更深的层级检测到循环,则返回 true。
            if (flag) return true;
        }
    }

    // 如果没有检测到循环,则返回 false。
    return false;
}

// 示例使用:
const a = 1;
const b = {a};
const c = {b};
const o = {d: {a: 3}, c};
o.c.b.aa = a;  // 这会创建一个循环引用。

console.log(isCycleObject(o));  // 由于循环引用,这将输出 true。

18.LazyMan函数

// LazyMan(“Hank”)
// 输出
// Hi! This is Hank!
// ---------------------------------------------
// LazyMan(“Hank”).sleep(10).eat(“dinner”)
// 输出
// Hi! This is Hank!
// 等待10秒..
// Wake up after 10
// Eat dinner~
//---------------------------------------------
// LazyMan(“Hank”).eat(“dinner”).eat(“supper”)
// 输出
// Hi This is Hank!
// Eat dinner~
// Eat supper~
//---------------------------------------------
// LazyMan(“Hank”).eat(“supper”).sleepFirst(5)
// 输出
// 等待5秒
// Wake up after 5
// Hi This is Hank!
// Eat supper
class LazyMan {
    constructor(name) {
        this.name = name;
        this.tasks = []; // 任务队列

        // 向任务队列中添加任务,用于打印"Hi This is ${name}!"
        this.tasks.push(() => {
            setTimeout(() => {
                console.log(`Hi, This is ${name}!`);
                this.next();
            }, 0);
        });

        setTimeout(() => {
            this.next();
        }, 0);
    }

    // 执行任务队列中的下一个任务
    next() {
        let task = this.tasks.shift();
        task && task();
    }

    // 向任务队列中添加任务,用于打印"Eat ${food} ~"
    eat(food) {
        this.tasks.push(() => {
            setTimeout(() => {
                console.log(`Eat ${food} ~`);
                this.next();
            });
        });
        return this;
    }

    // 向任务队列中添加任务,用于打印"Wake up after ${time}"
    sleep(time) {
        this.tasks.push(() => {
            setTimeout(() => {
                console.log(`Wake up after ${time}`);
                this.next();
            }, time * 1000);
        });
        return this;
    }

    // 向任务队列中添加任务,用于打印"Wake up after ${time}",并将该任务放到队列开头
    sleepFirst(time) {
        this.tasks.unshift(() => {
            setTimeout(() => {
                console.log(`Wake up after ${time}`);
                this.next();
            }, time * 1000);
        });
        return this;
    }
}

19.# 实现一个request/Fetch最大并发控制机

function request(urls, maxNumber, callback) 要求编写函数实现,根据urls数组内的url地址进行并发网络请求,最大并发数maxNumber,当所有请求完毕后调用callback函数(已知请求网络的方法可以使用fetch api)。和上面的promise scheduler类似。

function request(urls, maxNumber, callback) {
    let queue = [];
    let currentJobs = 0;
    let results = [];

    const run = function() {
        if (queue.length === 0 || currentJobs >= maxNumber) return;

        currentJobs++;
        const url = queue.shift();
        console.log('started', url);
        fetch(url)
            .then(response => response.json())
            .then(data => {
                console.log('finshed', url);
                currentJobs--;
                results.push({ res: data, time: new Date() });

                if (results.length === urls.length) {
                    callback(results);
                    return;
                }
                run();
            })
            .catch(error => {
                console.log('finshed', url);
                currentJobs--;
                results.push({ error, time: new Date() });

                if (results.length === urls.length) {
                    callback(results);
                    return;
                }
                run();
            });
    };

    urls.forEach(url => {
        queue.push(url);
        run(url);
    });
}

const urls = [
   "https://www.badiu.com",
   "https://www.badiu.com",
   "https://www.badiu.com",
   "https://www.badiu.com",
   "https://www.badiu.com",
];

console.log(request(urls, 3, res => {
    console.log(res);
}));