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)?请写出具体代码
8.如何使用JS判断对象是否存在循环引用?请写出具体代码
9.如何使用JS实现日期格式化函数?请写出具体代码
10.如何使用JS实现字符串的repeat方法?请写出具体代码
11.如何使用JS实现字符串翻转?请写出具体代码
12.如何用JS实现将数字每干分位用逗号隔开(1000转为1,000)?请写出具体代码
13.如何使用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)异步执行:回调函数需要异步执行。