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()
方法,你需要更复杂的逻辑,包括:
- 创建一个新的
Promise
。 - 在
onFulfilled
或onRejected
回调函数中,根据执行结果来解决或拒绝这个新的Promise
。 - 确保
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!"
在这个实现中,我们做了以下几件事:
-
处理
context
:如果context
是null
或undefined
,我们将其设置为全局对象globalThis
(在浏览器中是window
,在 Node.js 中是global
)。 -
创建唯一属性名:使用
Symbol()
创建一个唯一的符号,以确保我们不会在context
对象上意外地覆盖任何现有属性。 -
绑定函数:将调用
myCall
的函数(即this
)赋值给context
对象上的一个临时属性。 -
调用函数:使用展开运算符
...args
将参数传递给函数,并调用它。 -
清理:删除我们在
context
对象上创建的临时属性,以避免污染对象。 -
返回结果:返回函数的调用结果。
请注意,这个实现没有处理一些边缘情况,比如当 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
方法的流程思路和具体代码:
流程思路
-
检查
this
:确保this
指向的是一个函数,因为apply
是函数的方法。 -
处理
context
:apply
的第一个参数是context
,即this
在函数体内应该引用的对象。如果context
是null
或undefined
,则默认绑定到全局对象(在浏览器中是window
,在严格模式下或 Node.js 中是undefined
)。但出于兼容性考虑,我们通常将null
和undefined
转换为全局对象(或简单地处理为undefined
,取决于实现)。 -
处理参数:
apply
的第二个参数是一个数组(或类数组对象),包含要传递给函数的参数。我们需要将这些参数从数组中取出,并逐个传递给函数。 -
创建临时属性:为了在
context
上调用函数,我们可以创建一个临时属性,将当前函数(即调用apply
的函数)赋值给这个属性。为了避免属性名冲突,可以使用Symbol
来生成一个唯一的属性名。但是,由于apply
通常不需要频繁调用,且属性名冲突的概率较低,我们也可以简单地使用一个不太可能冲突的字符串作为属性名。 -
调用函数:使用
Function.prototype.call
或其他方法(但为了避免递归调用我们自己的apply
实现,通常不这样做)在context
上调用这个临时属性所指向的函数,并传递从数组中取出的参数。 -
删除临时属性:调用函数后,删除在
context
上创建的临时属性,以避免污染对象。 -
返回结果:返回函数的调用结果。
具体代码
以下是基于上述流程思路的 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
方法的流程思路和具体代码:
流程思路
-
检查
this
:确保this
指向的是一个函数,因为bind
是函数的方法。 -
处理
context
:bind
的第一个参数是context
,即this
在新函数体内应该引用的对象。如果context
是null
或undefined
,则在新函数中this
将保持为undefined
(在非严格模式下,null
或undefined
作为this
值时会被自动替换为全局对象,但bind
的行为应该与严格模式一致,即保持为null
或undefined
)。 -
处理参数:
bind
可以接受除了context
之外的额外参数,这些参数会在新函数调用时前置到原始函数调用的参数之前。 -
创建新函数:返回一个新的函数,这个新函数在被调用时会使用提供的
context
和前置参数。 -
在新函数中调用原始函数:使用
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. 使用递归实现深拷贝
这是最常见且基础的方式,通过递归遍历对象的所有属性(包括嵌套对象),并复制它们到新对象中。这种方法可以处理大多数情况,但对于包含循环引用的对象或特殊类型(如函数、
undefined
、Symbol
等)可能需要额外的处理。2. 使用
JSON.parse
和JSON.stringify
这种方法非常简单,但有一些限制。它不能处理
undefined
、函数、Symbol
、BigInt
、Date
(会转换为字符串)、Map
、Set
、Blob
、File
等特殊类型,并且会丢失对象的原型链和不可枚举属性。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
代码解释:
- 处理 null 和非对象类型:
- 如果输入的是
null
或者不是对象类型(如字符串、数字、布尔值等),直接返回该值。
- 如果输入的是
- 处理循环引用:
- 使用
WeakMap
来存储已经复制过的对象,避免循环引用导致的无限递归。 - 如果对象已经存在于
WeakMap
中,则直接返回该对象的副本。
- 使用
- 处理 Date 类型:
- 如果对象是
Date
类型,则创建一个新的Date
对象并返回。
- 如果对象是
- 处理 Array 类型:
- 如果对象是数组,则创建一个新的数组,并递归地复制数组中的每个元素。
- 处理普通对象:
- 对于普通对象,创建一个新的空对象,并递归地复制对象的每个属性。
- 测试用例:
- 创建一个包含各种类型(包括嵌套对象和循环引用)的复杂对象,并测试深拷贝函数。
这个深拷贝函数能够处理大多数常见的 JavaScript 对象类型,包括普通对象、数组、日期和循环引用。