Javascript手撕题目大全(回顾四)

 1.如何使用JS实现Promise 对象?请写出具体代码

Promise其实也不难-CSDN博客

Javascript 手写一个Promise_javascript中手写promise ?-CSDN博客

如何使用 JS 实现 Promise 对象?请写出具体代码 - 前端手写代码面试题 - 面试鸭 - 程序员求职面试刷题神器

题目要求我们使用JavaScript实现一个Promise对象。对此我们可以基于Promise/A+规范的要求进行实现Promise/A+规范是对Promise行为的详细描述确保不同的Promise 实现可以互操作。实现一个符合Promise/A+规范的Promise对象需要处理以下几个核心概念:
1)三种状态: pending (进行中)、fulfilled (已成功)、rejected (已失败)
2)状态不可逆:状态一旦从pending转变为fulfilled 或rejected,就不可再改变。

3 ) then方法:用于注册回调函数,处理Promise的成功值或失败原因。
4)异步执行:回调函数需要异步执行。

class MyPromise {
    constructor(executor) {
        this.state = 'pending'; // 初始状态
        this.value = undefined; // 成功值
        this.reason = undefined; // 失败原因
        this.onFulfilledCallbacks = []; // 成功回调队列
        this.onRejectedCallbacks = []; // 失败回调队列

        const resolve = (value) => {
            if (this.state === 'pending') {
                this.state = 'fulfilled';
                this.value = value;
                this.onFulfilledCallbacks.forEach((callback) => callback());
            }
        };

        const reject = (reason) => {
            if (this.state === 'pending') {
                this.state = 'rejected';
                this.reason = reason;
                this.onRejectedCallbacks.forEach((callback) => callback());
            }
        };

        try {
            executor(resolve, reject);
        } catch (error) {
            reject(error);
        }
    }

    then(onFulfilled, onRejected) {
        onFulfilled =
            typeof onFulfilled === 'function' ? onFulfilled : (value) => value;
        onRejected =
            typeof onRejected === 'function'
                ? onRejected
                : (reason) => {
                      throw reason;
                  };

        const promise2 = new MyPromise((resolve, reject) => {
            if (this.state === 'fulfilled') {
                setTimeout(() => {
                    try {
                        const x = onFulfilled(this.value);
                        resolvePromise(promise2, x, resolve, reject);
                    } catch (error) {
                        reject(error);
                    }
                }, 0);
            } else if (this.state === 'rejected') {
                setTimeout(() => {
                    try {
                        const x = onRejected(this.reason);
                        resolvePromise(promise2, x, resolve, reject);
                    } catch (error) {
                        reject(error);
                    }
                }, 0);
            } else if (this.state === 'pending') {
                this.onFulfilledCallbacks.push(() => {
                    setTimeout(() => {
                        try {
                            const x = onFulfilled(this.value);
                            resolvePromise(promise2, x, resolve, reject);
                        } catch (error) {
                            reject(error);
                        }
                    }, 0);
                });

                this.onRejectedCallbacks.push(() => {
                    setTimeout(() => {
                        try {
                            const x = onRejected(this.reason);
                            resolvePromise(promise2, x, resolve, reject);
                        } catch (error) {
                            reject(error);
                        }
                    }, 0);
                });
            }
        });

        return promise2;
    }
}

function resolvePromise(promise2, x, resolve, reject) {
    if (promise2 === x) {
        return reject(new TypeError('Chaining cycle detected for promise'));
    }

    let called = false;
    if (x !== null && (typeof x === 'object' || typeof x === 'function')) {
        try {
            const then = x.then;
            if (typeof then === 'function') {
                then.call(
                    x,
                    (y) => {
                        if (called) return;
                        called = true;
                        resolvePromise(promise2, y, resolve, reject);
                    },
                    (r) => {
                        if (called) return;
                        called = true;
                        reject(r);
                    }
                );
            } else {
                resolve(x);
            }
        } catch (error) {
            if (called) return;
            called = true;
            reject(error);
        }
    } else {
        resolve(x);
    }
}

// 使用示例
const promise = new MyPromise((resolve, reject) => {
    setTimeout(() => {
        resolve('Success');
    }, 1000);
});

promise
    .then((value) => {
        console.log(value); // 输出: 'Success'
        return 'Next success';
    })
    .then((value) => {
        console.log(value); // 输出: 'Next success'
    })
    .catch((error) => {
        console.error(error);
    });

​ 

const promise = new MyPromise((resolve, reject) => {
    setTimeout(() => {
        resolve('First success');
    }, 1000);
});

promise
    .then((value) => {
        console.log(value); // 输出: 'First success'
        return 'Second success';
    })
    .then((value) => {
        console.log(value); // 输出: 'Second success'
        return new MyPromise((resolve, reject) => {
            setTimeout(() => {
                resolve('Third success');
            }, 1000);
        });
    })
    .then((value) => {
        console.log(value); // 输出: 'Third success'
    })
    .catch((error) => {
        console.error(error);
    });

const promise = new MyPromise((resolve, reject) => {
    setTimeout(() => {
        reject('Error occurred');
    }, 1000);
});

promise
    .then((value) => {
        console.log(value);
    })
    .catch((error) => {
        console.error('Caught error:', error); // 输出: 'Caught error: Error occurred'
    });

MyPromise.prototype.finally = function (callback) {
    return this.then(
        (value) => MyPromise.resolve(callback()).then(() => value),
        (reason) =>
            MyPromise.resolve(callback()).then(() => {
                throw reason;
            })
    );
};

// 使用示例
const promise = new MyPromise((resolve, reject) => {
    setTimeout(() => {
        resolve('Success');
    }, 1000);
});

promise
    .then((value) => {
        console.log(value); // 输出: 'Success'
    })
    .finally(() => {
        console.log('Finally executed'); // 输出: 'Finally executed'
    });

MyPromise.resolve = function (value) {
    return new MyPromise((resolve, reject) => {
        resolve(value);
    });
};

MyPromise.reject = function (reason) {
    return new MyPromise((resolve, reject) => {
        reject(reason);
    });
};

// 使用示例
MyPromise.resolve('Resolved value').then((value) => {
    console.log(value); // 输出: 'Resolved value'
});

MyPromise.reject('Rejected reason').catch((reason) => {
    console.error(reason); // 输出: 'Rejected reason'
});

2.如何使用JS实现Promise的then方法?请写出具体代码

在 JavaScript 中,Promise 的 .then() 方法是用于处理 Promise 对象解决(resolve)或拒绝(reject)后的结果。Promise 构造函数本身并不直接提供 .then() 方法的实现,但你可以通过创建一个自定义的类来模拟 Promise 的行为,并在这个类中实现 .then() 方法。

然而,通常我们不需要自己实现 Promise 类和它的 .then() 方法,因为 JavaScript 的原生 Promise 已经提供了这些功能。不过,为了教育目的,我可以展示一个简化的 MyPromise 类,它模拟了原生 Promise 的一部分行为,并实现了 .then() 方法。

请注意,这个示例是为了教学目的而简化的,并不包含原生 Promise 的所有功能和错误处理机制。

class MyPromise {  
    constructor(executor) {  
        this.state = 'pending'; // 初始状态为pending  
        this.value = undefined; // 保存resolve的值  
        this.reason = undefined; // 保存reject的原因  
        this.onFulfilledCallbacks = []; // 成功回调函数的数组  
        this.onRejectedCallbacks = []; // 失败回调函数的数组  
  
        const resolve = (value) => {  
            if (this.state === 'pending') {  
                this.state = 'fulfilled';  
                this.value = value;  
                this.onFulfilledCallbacks.forEach(fn => fn());  
            }  
        };  
  
        const reject = (reason) => {  
            if (this.state === 'pending') {  
                this.state = 'rejected';  
                this.reason = reason;  
                this.onRejectedCallbacks.forEach(fn => fn());  
            }  
        };  
  
        try {  
            executor(resolve, reject);  
        } catch (error) {  
            reject(error);  
        }  
    }  
  
    then(onFulfilled, onRejected) {  
        if (this.state === 'fulfilled') {  
            onFulfilled(this.value);  
        } else if (this.state === 'rejected') {  
            onRejected(this.reason);  
        } else {  
            // 如果Promise还在pending状态,将回调函数添加到相应的数组中  
            this.onFulfilledCallbacks.push(() => onFulfilled(this.value));  
            this.onRejectedCallbacks.push(() => onRejected(this.reason));  
        }  
  
        // 注意:这里的实现没有返回一个新的Promise,也没有处理链式调用和异步执行的情况  
        // 这是一个非常简化的版本,仅用于演示目的  
        // 在实际使用中,你应该返回一个新的Promise来处理这些情况  
    }  
}  
  
// 使用MyPromise的示例(但请注意,这个示例不会按预期工作,因为它缺少了许多关键功能)  
// 下面的代码只是为了展示如何调用.then()方法,但它不会正确地处理异步操作  
const myPromise = new MyPromise((resolve, reject) => {  
    setTimeout(() => {  
        resolve('Success!');  
    }, 1000);  
});  
  
myPromise.then(value => {  
    console.log(value); // 预期输出: Success!,但实际上这个示例不会工作  
});  
  
// 要使上面的代码工作,你需要实现一个能够处理异步回调和链式调用的.then()方法  
// 这通常涉及到返回一个新的Promise,并在适当的时机调用resolve或reject

然而,上面的 MyPromise 类并没有正确地实现 .then() 方法,因为它没有处理异步回调和链式调用。在真实的 Promise 实现中,.then() 方法会返回一个新的 Promise,这个新的 Promise 会根据传入的 onFulfilled 或 onRejected 回调函数的执行结果来解决或拒绝。

为了正确地实现 .then() 方法,你需要更复杂的逻辑,包括:

  1. 创建一个新的 Promise
  2. 在 onFulfilled 或 onRejected 回调函数中,根据执行结果来解决或拒绝这个新的 Promise
  3. 确保 onFulfilled 和 onRejected 回调在适当的时机(即原始 Promise 解决或拒绝后)以异步方式执行。

3.如何使用JS实现Promise的all方法?请写出具体代码

function myPromiseAll(promises) {  
    return new Promise((resolve, reject) => {  
        const results = []; // 用于存储每个Promise的解决值  
        let count = 0; // 已解决的Promise计数器  
  
        // 遍历promises数组,为每个Promise添加.then()处理器  
        promises.forEach((promise, index) => {  
            promise.then(  
                value => {  
                    results[index] = value; // 存储解决值  
                    count++; // 增加计数器  
                    // 如果所有Promise都已解决,则调用resolve  
                    if (count === promises.length) {  
                        resolve(results);  
                    }  
                },  
                reason => {  
                    // 如果有任何一个Promise被拒绝,则立即调用reject  
                    reject(reason);  
                }  
            );  
        });  
    });  
}  
  
// 使用myPromiseAll的示例  
const promise1 = Promise.resolve(1);  
const promise2 = Promise.resolve(2);  
const promise3 = Promise.resolve(3);  
  
myPromiseAll([promise1, promise2, promise3]).then(values => {  
    console.log(values); // 输出: [1, 2, 3]  
});  
  
// 另一个示例,其中一个Promise被拒绝  
const promise4 = Promise.resolve(4);  
const promise5 = Promise.reject('Error!');  
  
myPromiseAll([promise4, promise5]).catch(error => {  
    console.log(error); // 输出: Error!  
});

4.如何使用JS实现Promise的race方法?请写出具体代码

在 JavaScript 中,Promise.race 方法接受一个可迭代对象(通常是数组)作为输入,该对象包含多个 Promise 对象(或可转换为 Promise 的对象)。它返回一个新的 Promise,该 Promise 的解决(resolve)或拒绝(reject)状态由第一个解决或拒绝的输入 Promise 决定。如果第一个 Promise 解决,则返回的 Promise 也以相同的值解决;如果第一个 Promise 拒绝,则返回的 Promise 也以相同的拒绝原因被拒绝。

下面是一个简化的 myPromiseRace 函数,它模拟了 Promise.race 的行为:

function myPromiseRace(promises) {  
    return new Promise((resolve, reject) => {  
        // 遍历promises数组,为每个Promise添加.then()和.catch()处理器  
        promises.forEach(promise => {  
            promise.then(value => {  
                // 如果当前Promise解决,则立即解决返回的Promise  
                resolve(value);  
                // 注意:一旦resolve或reject被调用,Promise的状态就变为final,后续的.then或.catch不会被执行  
            }).catch(reason => {  
                // 如果当前Promise拒绝,则立即拒绝返回的Promise  
                reject(reason);  
            });  
              
            // 为了防止多个Promise同时解决或拒绝时导致多次调用resolve或reject,  
            // 我们不需要额外的逻辑来阻止这种情况,因为Promise规范保证了resolve和reject只会被调用一次。  
        });  
    });  
}  
  
// 使用myPromiseRace的示例  
const promise1 = new Promise((resolve) => setTimeout(resolve, 500, 'one'));  
const promise2 = new Promise((resolve, reject) => setTimeout(reject, 100, 'two'));  
  
myPromiseRace([promise1, promise2]).then(value => {  
    console.log(value); // 不会执行,因为promise2更快地被拒绝了  
}).catch(reason => {  
    console.log(reason); // 输出: two  
});  
  
// 另一个示例,其中promise1更快地解决  
const promise3 = new Promise((resolve) => setTimeout(resolve, 100, 'three'));  
const promise4 = new Promise((resolve) => setTimeout(resolve, 200, 'four'));  
  
myPromiseRace([promise3, promise4]).then(value => {  
    console.log(value); // 输出: three  
}).catch(reason => {  
    // 不会执行,因为所有Promise都解决了  
});

在这个实现中,我们为输入的每个 Promise 对象添加了 .then() 和 .catch() 处理器。由于 Promise 的规范保证了 resolve 和 reject 函数只会被调用一次,并且一旦 Promise 的状态变为 final(即已解决或已拒绝),后续的 .then() 或 .catch() 回调将不会被执行,因此我们不需要额外的逻辑来防止多次调用 resolve 或 reject

 1.如何使用JS模拟实现instanceof 操作符?请写出具体代码

方法描述优点缺点
typeof 运算符返回变量的数据类型(对于基本类型很有效,但对于对象和数组返回 "object"简洁易用,适用于基本类型判断无法准确判断 null(返回 "object")和复杂对象/数组的类型
instanceof 运算符检查对象是否是特定构造函数的实例(适用于对象和数组)可以用于判断对象和数组的类型,以及它们是否属于某个构造函数原型链依赖于原型链,可能受到原型链修改的影响,不适用于基本类型判断
Array.isArray() 方法检查变量是否是数组专门用于数组判断,准确度高仅适用于数组判断
Object.prototype.toString.call()返回对象的内部 [[Class]] 属性的字符串表示,用于准确判断类型可以准确判断所有类型的变量,包括基本类型和复杂对象/数组稍显冗长,不如 typeof 和 instanceof 直观
constructor 属性返回创建对象的构造函数引用(可能被修改或不可依赖)在构造函数未被修改的情况下可以用于类型判断依赖于构造函数未被修改,可能不安全

       instanceof操作符用于检测构造函数的prototype属性是否出现在某个实例对象的原型链上。它的语法是object instanceof constructor,如果object的原型链中包含constructor.prototype,则返回true,否则返回false。

在JavaScript中,instanceof 操作符用于检测一个对象是否在其原型链的原型构造函数的 prototype 属性中存在。要模拟实现 instanceof 操作符,我们需要手动遍历对象的原型链,并检查是否存在与给定构造函数相关联的 prototype 对象。

以下是一个模拟实现 instanceof 的代码示例:

function myInstanceof(left, right) {  
    // 首先检查右值(构造函数)是否为函数,如果不是则直接返回 false  
    if (typeof right !== 'function') {  
        throw new TypeError('Right-hand side of `instanceof` is not callable');  
    }  
  
    // 获取右值(构造函数)的原型对象  
    const rightPrototype = right.prototype;  
  
    // 左值(实例对象)可能是 null 或 undefined,它们不是任何对象的实例  
    if (left === null || left === undefined) {  
        return false;  
    }  
  
    // 使用一个循环来遍历左值的原型链  
    let leftPrototype = Object.getPrototypeOf(left);  
    while (leftPrototype !== null) {  
        // 如果找到了与右值原型对象相同的原型,则返回 true  
        if (leftPrototype === rightPrototype) {  
            return true;  
        }  
        // 继续向上遍历原型链  
        leftPrototype = Object.getPrototypeOf(leftPrototype);  
    }  
  
    // 如果遍历完原型链都没有找到匹配的原型对象,则返回 false  
    return false;  
}  
  
// 测试代码  
function Person(name) {  
    this.name = name;  
}  
  
const alice = new Person('Alice');  
  
console.log(myInstanceof(alice, Person)); // true  
console.log(myInstanceof(alice, Object)); // true,因为 Person.prototype 继承自 Object.prototype  
console.log(myInstanceof(alice, Array));  // false  
console.log(myInstanceof(null, Object));  // false  
console.log(myInstanceof(undefined, Person)); // false

在这个实现中,我们首先检查 right 是否是一个函数,因为 instanceof 的右侧必须是一个构造函数(即函数)。然后,我们获取该构造函数的 prototype 属性。接下来,我们检查 left 是否为 null 或 undefined,因为这两个值不是任何对象的实例。

如果 left 是一个有效的对象,我们使用 Object.getPrototypeOf() 方法来获取它的原型对象,并在一个循环中遍历原型链。在每次迭代中,我们检查当前原型对象是否与 right 的原型对象相同。如果找到匹配项,则返回 true。如果遍历完整个原型链都没有找到匹配项,则返回 false

2.如何使用JS模拟实现new 操作符?请写出具体代码

在JavaScript中,new操作符用于创建一个用户定义的对象类型的实例。new操作符会执行以下步骤:

1)创建一个空的简单JavaScript对象(即{})。
2)将这个空对象的原型指向构造函数的prototype属性。

3)将这个空对象作为this的上下文执行构造函数。
4)如果构造函数返回一个对象,则返回该对象;否则,返回这个新创建的对象。
 

function myNew(Constructor, ...args) {  
    // 1. 创建一个新的空对象  
    const instance = {};  
  
    // 2. 将这个空对象的原型设置为构造函数的 prototype 属性  
    Object.setPrototypeOf(instance, Constructor.prototype);  
  
    // 或者使用更传统的方式(ES5及之前版本)  
    // instance.__proto__ = Constructor.prototype;  
    // 注意:__proto__ 属性虽然在现代JavaScript环境中仍然可用,但它不是标准的一部分,并且在严格模式下可能会被弃用或限制使用。  
  
    // 3. 绑定 this 到新创建的对象上,并调用构造函数,传入任何参数  
    const result = Constructor.apply(instance, args);  
  
    // 4. 如果构造函数返回了一个对象(而不是 undefined 或 null),则返回该对象;否则,返回新创建的对象  
    return (typeof result === 'object' && result !== null) ? result : instance;  
}  
  
// 测试代码  
function Person(name, age) {  
    this.name = name;  
    this.age = age;  
}  
  
const alice = myNew(Person, 'Alice', 30);  
  
console.log(alice.name); // Alice  
console.log(alice.age);  // 30  
console.log(alice instanceof Person); // true  
console.log(Object.getPrototypeOf(alice) === Person.prototype); // true

在这个实现中,myNew 函数接受一个构造函数 Constructor 和任意数量的参数 ...args。它首先创建一个空对象 instance,然后将该对象的原型设置为构造函数的 prototype 属性。接下来,它使用 apply 方法调用构造函数,将 this 绑定到新创建的对象上,并传入任何提供的参数。最后,它检查构造函数是否返回了一个对象;如果是,则返回该对象;否则,返回新创建的对象。

这个模拟实现的行为与内置的 new 操作符非常相似,但它是通过显式地执行上述步骤来实现的。

3.如何使用JS实现类型判断函数,实现和typeof 操作符一样的功能?请写出具体代码

在JavaScript中,typeof 操作符用于确定一个变量的类型。虽然我们不能完全复制 typeof 的内部机制(特别是它处理函数和原始类型时的特殊行为),但我们可以通过一系列的检查和条件语句来模拟它的功能。

以下是一个实现类型判断函数的代码示例,它尝试模拟 typeof 操作符的行为:

function myTypeof(value) {  
    // 处理 null 类型,因为 typeof null 返回 'object',这是一个 JavaScript 的历史遗留问题  
    if (value === null) {  
        return 'object';  
    }  
  
    // 处理未定义类型  
    if (value === undefined) {  
        return 'undefined';  
    }  
  
    // 处理布尔类型  
    if (typeof value === 'boolean') {  
        return 'boolean';  
    }  
  
    // 处理数字类型(包括 NaN,尽管 typeof NaN 返回 'number')  
    if (typeof value === 'number') {  
        return 'number';  
    }  
  
    // 处理字符串类型  
    if (typeof value === 'string') {  
        return 'string';  
    }  
  
    // 处理 Symbol 类型(ES6 引入)  
    if (typeof value === 'symbol') {  
        return 'symbol';  
    }  
  
    // 处理函数类型(包括类构造函数和箭头函数等)  
    if (typeof value === 'function') {  
        return 'function';  
    }  
  
    // 处理对象类型(包括数组、普通对象、Date、RegExp 等)  
    // 注意:这里不区分数组和其他对象类型,因为 typeof [] 也返回 'object'  
    if (value && typeof value === 'object') {  
        // 可以添加额外的检查来识别特定的对象类型,比如数组  
        // 但为了模拟 typeof 的行为,我们只需返回 'object'  
        return 'object';  
    }  
  
    // 如果以上条件都不满足,理论上这里不应该有值能够到达  
    // 但为了代码的健壮性,我们可以返回一个默认值或抛出一个错误  
    // 在这个例子中,我们返回一个默认值 'unknown'  
    return 'unknown';  
}  
  
// 测试代码  
console.log(myTypeof(null));       // 'object'  
console.log(myTypeof(undefined));  // 'undefined'  
console.log(myTypeof(true));       // 'boolean'  
console.log(myTypeof(42));         // 'number'  
console.log(myTypeof('hello'));    // 'string'  
console.log(myTypeof(Symbol('s'))); // 'symbol'  
console.log(myTypeof(function() {})); // 'function'  
console.log(myTypeof({}));         // 'object'  
console.log(myTypeof([]));         // 'object'(注意:不区分数组和其他对象)  
console.log(myTypeof(new Date())); // 'object'(同样不区分)

需要注意的是,这个实现函数 myTypeof 在处理对象类型时并不区分数组、普通对象、Date 对象、RegExp 对象等,因为 typeof 操作符本身也不做这种区分。如果你需要更精细的类型检查,你可能需要使用其他方法,比如 Array.isArray()instanceof 操作符或者 Object.prototype.toString.call() 方法

另外,myTypeof 函数在处理 null 时返回 'object',这是为了模拟 typeof 的一个已知“缺陷”。在JavaScript中,typeof null 意外地返回 'object',这是一个历史遗留问题,并且在ECMAScript标准中被保留了下来。

Object.prototype.toString.call() 方法在 JavaScript 中是一个非常有用的工具,用于获取一个对象的内部 [[Class]] 属性,这个属性通常对应于该对象的构造函数名称(以字符串形式表示),并且可以用于更精确地确定对象的类型。

下面是一个使用 Object.prototype.toString.call() 的示例,该示例展示了如何判断不同类型的变量:

function getType(value) {  
    return Object.prototype.toString.call(value).slice(8, -1).toLowerCase();  
}  
  
// 测试代码  
console.log(getType([]));           // "array"  
console.log(getType({}));           // "object"  
console.log(getType("string"));     // "string"  
console.log(getType(7));            // "number"  
console.log(getType(true));         // "boolean"  
console.log(getType(null));         // "null"  
console.log(getType(undefined));    // "undefined"(注意:这里实际上会抛出TypeError,因为undefined不是对象)  
console.log(getType(/regex/));      // "regexp"  
console.log(getType(new Date()));   // "date"  
console.log(getType(new Error()));  // "error"  
  
// 注意:对于函数和类构造函数,Object.prototype.toString.call() 也会返回 "[object Function]"  
function testFunc() {}  
console.log(getType(testFunc));     // "function"  
  
// 对于undefined的处理,需要单独判断,因为undefined不是对象,不能直接调用Object.prototype.toString.call()  
try {  
    console.log(getType(undefined)); // 这行会抛出TypeError  
} catch (e) {  
    console.log("undefined cannot be processed with Object.prototype.toString.call() directly.");  
}  
  
// 为了处理undefined,可以修改getType函数,添加额外的检查  
function getSafeType(value) {  
    return value === undefined ? 'undefined' : Object.prototype.toString.call(value).slice(8, -1).toLowerCase();  
}  
  
console.log(getSafeType(undefined)); // "undefined"

在上面的代码中,getType 函数使用 Object.prototype.toString.call(value) 来获取对象的内部 [[Class]] 属性的字符串表示,然后通过 slice(8, -1) 截取掉字符串的前缀 [object 和后缀 ],并将结果转换为小写字母,以便更容易地比较和识别类型。

然而,需要注意的是,对于 undefined,由于它不是对象,直接调用 Object.prototype.toString.call(undefined) 会抛出 TypeError。因此,在上面的代码中,我添加了一个 try...catch 块来捕获这个错误,并在注释中指出了这一点。为了处理这种情况,我提供了一个修改后的函数 getSafeType,它在尝试获取类型之前先检查值是否为 undefined

另外,虽然 Object.prototype.toString.call() 可以提供比 typeof 更精确的类型信息(特别是对于数组和 null 的处理上),但它也有一些局限性,null和 undefined,直接使用这种方法可能不太方便或根本不可行。在这些情况下,通常还是使用 typeof 操作符更为合适。

4.如何使用JS实现call方法?请写出具体代码

在 JavaScript 中,call 方法是 Function.prototype 上的一个方法,它允许你调用一个函数,同时设置函数体内 this 的值和参数。要实现一个类似于原生 call 方法的功能,我们需要创建一个自定义函数,并在该函数内部处理 this 绑定和参数传递。

以下是一个简单的实现,它模拟了 call 方法的基本行为:

Function.prototype.myCall = function(context, ...args) {  
    // 如果context是null或undefined,则默认绑定到全局对象(在浏览器中是window,在Node.js中是global)  
    context = context || globalThis;  
  
    // 创建一个唯一的属性名,用于在context上存储函数  
    const fnSymbol = Symbol();  
  
    // 将当前函数(即调用myCall的函数)赋值给context的一个临时属性  
    context[fnSymbol] = this;  
  
    // 调用context上的这个函数,并传入参数  
    const result = context[fnSymbol](...args);  
  
    // 删除临时属性,清理环境  
    delete context[fnSymbol];  
  
    // 返回调用结果  
    return result;  
};  
  
// 测试代码  
function greet(greeting, punctuation) {  
    return `${greeting}, my name is ${this.name}${punctuation}`;  
}  
  
const person = {  
    name: 'Alice'  
};  
  
console.log(greet.myCall(person, 'Hello', '!')); // 输出: "Hello, my name is Alice!"

在这个实现中,我们做了以下几件事:

  1. 处理 context:如果 context 是 null 或 undefined,我们将其设置为全局对象 globalThis(在浏览器中是 window,在 Node.js 中是 global)。

  2. 创建唯一属性名:使用 Symbol() 创建一个唯一的符号,以确保我们不会在 context 对象上意外地覆盖任何现有属性。

  3. 绑定函数:将调用 myCall 的函数(即 this)赋值给 context 对象上的一个临时属性。

  4. 调用函数:使用展开运算符 ...args 将参数传递给函数,并调用它。

  5. 清理:删除我们在 context 对象上创建的临时属性,以避免污染对象。

  6. 返回结果:返回函数的调用结果。

请注意,这个实现没有处理一些边缘情况,比如当 context 是一个不可扩展的对象或冻结的对象时,尝试在其上添加属性会失败。此外,这个实现也没有处理可能的内部属性或方法名冲突,尽管使用 Symbol 减少了这种风险。然而,对于大多数日常用途来说,这个简单的实现应该足够了。

方法描述语法this指向参数传递方式返回值典型用例
bind创建一个新的函数,其this值被永久绑定到提供的对象上,并可以预设一些参数function.bind(thisArg[, arg1[, arg2[, ...]]])永久绑定到thisArg预设参数,调用时可传入新参数返回一个新函数显式指定函数执行时的上下文,创建偏函数
call调用一个函数,其this值被指定为提供的对象,参数以逗号分隔的形式传递function.call([thisArg[, arg1[, arg2[, ...]]]])调用时指定为thisArg以逗号分隔的参数列表函数调用的结果需要在特定上下文中调用函数时使用
apply调用一个函数,其this值被指定为提供的对象,参数以数组的形式传递function.apply(thisArg, [argsArray])调用时指定为thisArg以数组形式传递的参数函数调用的结果需要将参数作为数组传递时使用,如处理类数组对象

5.如何使用JS实现apply方法?请写出具体代码

在 JavaScript 中,apply 方法与 call 方法类似,都允许你调用一个函数,并显式地设置函数内部的 this 值。不同的是,apply 方法接受参数的方式是将参数作为一个数组(或类数组对象)传递,而不是像 call 方法那样逐个传递。

以下是实现 apply 方法的流程思路和具体代码:

流程思路

  1. 检查 this:确保 this 指向的是一个函数,因为 apply 是函数的方法。

  2. 处理 contextapply 的第一个参数是 context,即 this 在函数体内应该引用的对象。如果 context 是 null 或 undefined,则默认绑定到全局对象(在浏览器中是 window,在严格模式下或 Node.js 中是 undefined)。但出于兼容性考虑,我们通常将 null 和 undefined 转换为全局对象(或简单地处理为 undefined,取决于实现)。

  3. 处理参数apply 的第二个参数是一个数组(或类数组对象),包含要传递给函数的参数。我们需要将这些参数从数组中取出,并逐个传递给函数。

  4. 创建临时属性:为了在 context 上调用函数,我们可以创建一个临时属性,将当前函数(即调用 apply 的函数)赋值给这个属性。为了避免属性名冲突,可以使用 Symbol 来生成一个唯一的属性名。但是,由于 apply 通常不需要频繁调用,且属性名冲突的概率较低,我们也可以简单地使用一个不太可能冲突的字符串作为属性名。

  5. 调用函数:使用 Function.prototype.call 或其他方法(但为了避免递归调用我们自己的 apply 实现,通常不这样做)在 context 上调用这个临时属性所指向的函数,并传递从数组中取出的参数。

  6. 删除临时属性:调用函数后,删除在 context 上创建的临时属性,以避免污染对象。

  7. 返回结果:返回函数的调用结果。

具体代码

以下是基于上述流程思路的 apply 方法的实现:

Function.prototype.myApply = function(context, args) {  
    // 检查 this 是否为函数  
    if (typeof this !== 'function') {  
        throw new TypeError('Apply must be called on a function');  
    }  
  
    // 处理 context 为 null 或 undefined 的情况  
    context = context || globalThis; // 在浏览器中,globalThis 通常是 window;在 Node.js 中是 global,但在严格模式下可能是 undefined  
  
    // 创建一个唯一的属性名(或者使用不太可能冲突的字符串,如 '_myApplyFunc_')  
    // const fnSymbol = Symbol('fn'); // 可以使用 Symbol,但出于简化,这里使用字符串  
    const fnPropertyName = '_myApplyFunc_';  
  
    // 将当前函数赋值给 context 的一个临时属性  
    context[fnPropertyName] = this;  
  
    // 调用 context 上的这个函数,并传入从 args 数组中展开的参数  
    const result = context[fnPropertyName](...(args || [])); // 如果 args 是 undefined 或 null,则使用空数组  
  
    // 删除临时属性  
    delete context[fnPropertyName];  
  
    // 返回调用结果  
    return result;  
};  
  
// 测试代码  
function greet(greeting, punctuation) {  
    return `${greeting}, my name is ${this.name}${punctuation}`;  
}  
  
const person = {  
    name: 'Alice'  
};  
  
console.log(greet.myApply(person, ['Hello', '!'])); // 输出: "Hello, my name is Alice!"

6.如何使用S实现bind方法?请写出具体代码

在 JavaScript 中,bind 方法允许你创建一个新的函数,这个新函数在被调用时会将其 this 关键字设置为提供的值,并且在调用时提供的参数会前置到原始函数调用的参数之前。

以下是实现 bind 方法的流程思路和具体代码:

流程思路

  1. 检查 this:确保 this 指向的是一个函数,因为 bind 是函数的方法。

  2. 处理 contextbind 的第一个参数是 context,即 this 在新函数体内应该引用的对象。如果 context 是 null 或 undefined,则在新函数中 this 将保持为 undefined(在非严格模式下,null 或 undefined 作为 this 值时会被自动替换为全局对象,但 bind 的行为应该与严格模式一致,即保持为 null 或 undefined)。

  3. 处理参数bind 可以接受除了 context 之外的额外参数,这些参数会在新函数调用时前置到原始函数调用的参数之前。

  4. 创建新函数:返回一个新的函数,这个新函数在被调用时会使用提供的 context 和前置参数。

  5. 在新函数中调用原始函数:使用 Function.prototype.apply 或 Function.prototype.call 在提供的 context 上调用原始函数,并传入前置参数和当前调用的参数。

具体代码

以下是基于上述流程思路的 bind 方法的实现:

Function.prototype.myBind = function(context, ...boundArgs) {  
    // 检查 this 是否为函数  
    if (typeof this !== 'function') {  
        throw new TypeError('Bind must be called on a function');  
    }  
  
    // 保存对原始函数的引用  
    const fn = this;  
  
    // 创建一个新函数  
    function boundFunction(...args) {  
        // 如果 boundFunction 是以 new 操作符调用的,则 this 应该指向新创建的对象  
        // 否则,使用提供的 context 作为 this 值(如果 context 是 null 或 undefined,则保持为 this 的当前值)  
        const thisArg = this instanceof boundFunction ? this : context;  
  
        // 调用原始函数,传入 context、前置参数和当前调用的参数  
        return fn.apply(thisArg, [...boundArgs, ...args]);  
    }  
  
    // 设置新函数的原型,以便在 new 操作符下正确工作  
    // 注意:这里假设原始函数是一个构造函数(即使用 new 调用的函数)  
    // 如果原始函数不是构造函数,则这一步可能是不必要的,或者应该抛出错误  
    // 在 ES5 中,可以通过 Object.create 来设置原型  
    // 在 ES6 中,可以使用 __proto__ 属性(但这不是标准属性,仅在大多数实现中可用)  
    // 这里我们采用一种简单但兼容的方法:通过构造函数间接设置原型  
    function EmptyFunction() {}  
    EmptyFunction.prototype = fn.prototype;  
    boundFunction.prototype = new EmptyFunction();  
  
    // 返回新函数  
    return boundFunction;  
};  
  
// 测试代码  
function greet(greeting, punctuation) {  
    return `${greeting}, my name is ${this.name}${punctuation}`;  
}  
  
const person = {  
    name: 'Alice'  
};  
  
const greetAlice = greet.myBind(person, 'Hello');  
console.log(greetAlice('!')); // 输出: "Hello, my name is Alice!"  
  
// 测试 new 操作符  
function Person(name) {  
    this.name = name;  
}  
  
Person.prototype.sayHello = function() {  
    return `Hello, my name is ${this.name}`;  
};  
  
const boundPerson = new (Person.myBind(null, 'Bob'))(); // 注意:这里 context 为 null,因为 Person 是构造函数  
console.log(boundPerson.sayHello()); // 输出: "Hello, my name is Bob"

 在这个实现中,我们确保了 this 是一个函数,并创建了一个新的函数 boundFunction。这个新函数在被调用时会使用提供的 context(或者在新实例的情况下使用 this)和前置参数。我们还设置了新函数的原型,以便在 new 操作符下正确工作。注意,这里的原型设置方法是一种兼容 ES5 的方法,它假设原始函数是一个构造函数。如果原始函数不是构造函数,则可能需要根据具体情况调整代码。

7.如何使用JS 实现一个深拷贝函数(Deep Copy)?请写出具体代码

实现深拷贝方式

1. 使用递归实现深拷贝

这是最常见且基础的方式,通过递归遍历对象的所有属性(包括嵌套对象),并复制它们到新对象中。这种方法可以处理大多数情况,但对于包含循环引用的对象或特殊类型(如函数、undefinedSymbol等)可能需要额外的处理。

2. 使用JSON.parseJSON.stringify

这种方法非常简单,但有一些限制。它不能处理undefined、函数、SymbolBigIntDate(会转换为字符串)、MapSetBlobFile等特殊类型,并且会丢失对象的原型链和不可枚举属性。

const copy = JSON.parse(JSON.stringify(original));

3. 使用第三方库

有许多第三方库提供了深拷贝功能,如lodash_.cloneDeep方法。这些库通常经过了广泛的测试,可以处理各种边缘情况,并且提供了易于使用的API。

const _ = require('lodash'); 
const copy = _.cloneDeep(original);

在 JavaScript 中实现一个深拷贝函数(Deep Copy)可以通过递归地复制对象的所有属性(包括嵌套对象)来实现。以下是一个实现深拷贝的示例代码:

function deepCopy(obj, hash = new WeakMap()) {  
    // 处理 null 和 非对象类型  
    if (obj === null || typeof obj !== 'object') {  
        return obj;  
    }  
  
    // 处理循环引用  
    if (hash.has(obj)) {  
        return hash.get(obj);  
    }  
  
    // 处理 Date 类型  
    if (obj instanceof Date) {  
        return new Date(obj);  
    }  
  
    // 处理 Array 类型  
    if (Array.isArray(obj)) {  
        const arrCopy = [];  
        hash.set(obj, arrCopy);  
        for (let i = 0; i < obj.length; i++) {  
            arrCopy[i] = deepCopy(obj[i], hash);  
        }  
        return arrCopy;  
    }  
  
    // 处理普通对象  
    const objCopy = {};  
    hash.set(obj, objCopy);  
    for (const key in obj) {  
        if (obj.hasOwnProperty(key)) {  
            objCopy[key] = deepCopy(obj[key], hash);  
        }  
    }  
  
    return objCopy;  
}  
  
// 测试用例  
const original = {  
    name: 'John',  
    age: 30,  
    date: new Date(),  
    nested: {  
        level: 1,  
        array: [1, 2, 3]  
    },  
    // 创建一个循环引用  
    self: null  
};  
original.self = original;  
  
const copy = deepCopy(original);  
  
console.log(copy);  
console.log(copy !== original); // true  
console.log(copy.nested !== original.nested); // true  
console.log(copy.date !== original.date); // true  
console.log(copy.self === copy); // true

代码解释:

  1. 处理 null 和非对象类型
    • 如果输入的是 null 或者不是对象类型(如字符串、数字、布尔值等),直接返回该值。
  2. 处理循环引用
    • 使用 WeakMap 来存储已经复制过的对象,避免循环引用导致的无限递归。
    • 如果对象已经存在于 WeakMap 中,则直接返回该对象的副本。
  3. 处理 Date 类型
    • 如果对象是 Date 类型,则创建一个新的 Date 对象并返回。
  4. 处理 Array 类型
    • 如果对象是数组,则创建一个新的数组,并递归地复制数组中的每个元素。
  5. 处理普通对象
    • 对于普通对象,创建一个新的空对象,并递归地复制对象的每个属性。
  6. 测试用例
    • 创建一个包含各种类型(包括嵌套对象和循环引用)的复杂对象,并测试深拷贝函数。

这个深拷贝函数能够处理大多数常见的 JavaScript 对象类型,包括普通对象、数组、日期和循环引用。
 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

FE_Jinger

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值