1.以下哪个不是DOM的标准属性
a.siblingNode b.firstChild c.parentNode d.children 这段文本是一个关于DOM(文档对象模型)标准属性的选择题,目的是让答题者从给出的四个选项中选出不属于DOM标准属性的那一项。下面对每个选项进行分析:
选项a:siblingNode
在DOM标准中,并没有siblingNode这个属性。不过,有previousSibling和nextSibling属性,它们分别用于获取当前节点的前一个兄弟节点和后一个兄弟节点。
选项b:firstChild
firstChild是DOM的标准属性,它用于获取当前节点的第一个子节点。例如,在一个HTML元素节点中,使用firstChild可以获取到该元素的第一个子元素或者文本节点。
选项c:parentNode
parentNode也是DOM的标准属性,它用于获取当前节点的父节点。通过这个属性,我们可以在DOM树中向上导航,找到当前节点的父元素。
选项d:children
children同样是DOM的标准属性,它返回一个包含当前元素所有子元素节点的HTMLCollection。需要注意的是,它只包含元素节点,不包含文本节点等其他类型的节点。
综上所述,答案是选项a,因为siblingNode不是DOM的标准属性。
编程题 手写Promise.any
Promise.any 是 ES2021 引入的一个新特性,它接收一个可迭代对象(通常是数组),该对象包含多个 Promise,只要其中任何一个 Promise 成功解决(fulfilled),Promise.any 返回的 Promise 就会以该 Promise 的解决值解决。如果所有的 Promise 都被拒绝(rejected),则返回的 Promise 会以一个 AggregateError 拒绝,该错误包含所有被拒绝的原因。
以下是手写 Promise.any 的实现代码:
javascript
function promiseAny(promises) {
return new Promise((resolve, reject) => {
let errors [];
let completedCount = 0;
if (promises.length === 0) {
return reject(new AggregateError([], 'All promises were rejected'));
}
promises.forEach((promise, index) => {
Promise.resolve(promise)
.then((value) => {
resolve(value);
})
.catch((error) => {
errors[index] = error;
completedCount++;
if (completedCount === promises.length) {
reject(new AggregateError(errors, 'All promises were rejected'));
}
});
});
});
}
// 使用示例
const promise1 = Promise.reject(new Error('Error 1'));
const promise2 = Promise.resolve('Success');
const promise3 = Promise.reject(new Error('Error 2'));
promiseAny([promise1, promise2, promise3])
.then((value) => {
console.log('Resolved with value:', value);
})
.catch((error) => {
console.error('Rejected with error:', error);
});
代码解释:
-
返回一个新的 Promise:
promiseAny函数返回一个新的 Promise 对象,这是Promise.any的基本要求。 -
处理空数组情况:如果传入的
promises数组为空,直接拒绝该 Promise,并抛出一个AggregateError。 -
遍历 Promise 数组:使用
forEach方法遍历传入的promises数组。 -
将每个元素转换为 Promise:使用
Promise.resolve将每个元素转换为 Promise,确保传入的元素可以被正确处理。 -
处理 Promise 结果:
- 如果任何一个 Promise 成功解决,调用
resolve方法并传入该 Promise 的解决值。 - 如果一个 Promise 被拒绝,将错误信息存储在
errors数组中,并增加completedCount。 - 当所有的 Promise 都被处理后,如果
completedCount等于promises数组的长度,说明所有的 Promise 都被拒绝,此时调用reject方法并抛出一个AggregateError,包含所有被拒绝的原因。
- 如果任何一个 Promise 成功解决,调用
通过上述代码和解释,你可以实现一个简单的 Promise.any 函数。
考察正则的,实现一个模版引擎 (类似EJS、Pug)
下面为你实现一个简单的模板引擎,其原理是借助正则表达式对模板字符串里的占位符进行匹配,然后用实际的数据替换这些占位符。
代码实现
javascript
function templateEngine(template, data) {
// 定义正则表达式,用于匹配 {{ }} 包裹的变量名
const regex = /{{([^}]+)}}/g;
// 使用 replace 方法替换匹配到的占位符
return template.replace(regex, (match, variable) => {
// 去除变量名前后的空格
variable = variable.trim();
// 从 data 对象中获取变量的值
return data[variable] !== undefined ? data[variable] : '';
});
}
// 使用示例
const template = '你好,{{name}}!你今年 {{age}} 岁了。';
const data = {
name: '张三',
age: 25
};
const result = templateEngine(template, data);
console.log(result);
代码解释
- 正则表达式定义:
/{{([^}]+)}}/g这个正则表达式的作用是匹配所有被{{ }}包裹的内容。其中([^}]+)是一个捕获组,用于捕获{{ }}中间的变量名。 replace方法:template.replacegex, callback)会template字符串里查找所有和正则表达式匹配的部分,并且用callback函数的返回值替换这些匹配项。callback函数:该函数接收两个参数,match是匹配到的完整字符串(例如{{name}}),variable是捕获组里的内容(例如name)。- 变量值获取:从
data对象中获取变量的值,若变量存在就返回其值,不存在则返回空字符串。
局限性
- 此模板引擎仅支持简单的变量替换,不支持条件判断、循环等复杂逻辑。
- 没有对特殊字符进行转义处理,在处理包含 HTML 标签等特殊字符的数据时可能会有安全问题。
若要实现更复杂的模板引擎,可参考 EJS、Pug 等成熟模板引擎的实现。
实现Promise串行执行,并在失败时重试指定次数的函数。如果所有重试都失败,则整个序列会停止执行
功能概述
要编写一个函数,这个函数的主要作用是让多个 Promise 按照顺序依次执行,也就是串行执行。并且,当某个 Promise 执行失败时,会对这个失败的 Promise 进行指定次数的重试。如果经过指定次数的重试后,这个 Promise 仍然失败,那么整个 Promise 序列就会停止执行,不会再继续执行后续的 Promise。
类比理解
可以把这个过程想象成一个流水线作业。每个 Promise 就像是流水线上的一个工序,工序需要按顺序依次完成。如果某个工序出现了问题(对应 Promise 执行失败),会尝试重新做这个工序指定的次数。如果尝试了指定次数后,这个工序还是无法完成,那么整个流水线就会停止工作,不会再进行后续的工序。
代码实现示例
以下是一个实现该功能的 JavaScript 代码示例:
javascript
function promiseSerialWithRetry(promises, maxRetries) {
let index = 0;
async function executeWithRetry() {
if (index >= promises.length) {
return;
}
let retries = 0;
while (retries < maxRetries) {
try {
const result = await promises[index]();
console.log(`Promise ${index} succeeded:`, result);
index++;
return executeWithRetry();
} catch (error) {
console.log(`Promise ${index} failed (attempt ${retries + 1}):`, error);
retries++;
}
}
console.log(`Promise ${index} failed after ${maxRetries} retries. Stopping sequence.`);
}
return executeWithRetry();
}
// 示例使用
const promise1 = () => new Promise((resolve, reject) => {
setTimeout(() => Math.random() > 0.5 ? resolve('Promise 1 success') : reject(new Error('Promise 1 failed')), 100);
});
const promise2 = () => new Promise((resolve, reject) => {
setTimeout(() => Math.random() > 0.5 ? resolve('Promise 2 success') : reject(new Error('Promise 2 failed')), 100);
});
const promise3 = () => new Promise((resolve, reject) => {
setTimeout(() => Math.random() > 0.5 ? resolve('Promise 3 success') : reject(new Error('Promise 3 failed')), 100);
});
const promises = [promise1, promise2, promise3];
const maxRetries = 3;
promiseSerialWithRetry(promises, maxRetries)
.then(() => console.log('All promises executed.'))
.catch(error => console.error('Sequence stopped due to error:', error));
在这个代码中,promiseSerialWithRetry 函数接收一个 Promise 数组 promises 和最大重试次数 maxRetries 作为参数。函数内部使用递归的方式依次执行每个 Promise,并在失败时进行重试。如果重试次数达到上限仍然失败,就会停止整个序列的执行。
实现一个compile函数,支持多级引用变量以及数组,能够解析嵌套的属性和数组索引
文章中已经给出了两种实现 compile 函数的方法,下面为你详细介绍这两种方法:
方法一:使用正则表达式手动解析
javascript
function compile(str, obj) {
// 匹配 ${...} 形式的插值表达式
const exp = /${([^}]+)}/g;
function matchFn(match, p1) {
let curObj = obj;
let value;
// 将变量路径按 . 分割成多个部分
p1.split(".").forEach((part) => {
// 匹配数组索引形式,如 arr[0]
const arrExp = /(\w+)[(\d+)]/;
const matchRes = part.match(arrExp);
if (matchRes) {
const [_, name, index] = matchRes;
value = curObj[name] && curObj[name][index];
if (value && typeof value !== "object") {
return value;
}
if (!value) throw new Error(`Property not found: ${match}`);
curObj = value;
} else {
value = curObj[part];
if (value && typeof value !== "object") {
return value;
}
if (!value) throw new Error(`Property not found: ${match}`);
curObj = value;
}
});
return value;
}
// 使用 matchFn 替换插值表达式
return str.replace(exp, matchFn);
}
// 示例使用
const template = "Hello, ${user.name}! Your balance is ${user.balance}. You have ${user.items[0]} in your cart. and ${user.items[2].kk}";
const exprObj = {
user: {
name: "Alice",
balance: 100.5,
items: ["Item1", "Item2", { kk: 1 }],
},
};
const compiledString = compile(template, exprObj);
console.log(compiledString);
解释:
- 首先使用正则表达式
/${([^}]+)}/g匹配字符串中所有${...}形式的插值表达式。 - 对于每个匹配到的插值表达式,将其内部的变量路径按
.分割成多个部分。 - 遍历这些部分,如果遇到数组索引形式(如
arr[0]),则解析数组索引并获取对应的值;否则直接对象属性的值。 - 最后使用
replace方法将插值表达式替换为实际的值。
方法二:使用 with 函数实现
javascript
function compile(template, context) {
// 将模板中的插值表达式 {{...}} 替换为 JavaScript 模板字符串语法 ${...}
const compiledTemplate = template.replace(
/{{(.*?)}}/g,
(match, p1) => `${${p1.trim()}}`
);
// 动态生成一个函数,用于替换模板中的插值表达式
const compiledFunction = new Function(
"context",
`
with (context) {
return `${compiledTemplate}`;
}
`
);
// 调用函数并返回结果
return compiledFunction(context);
}
// 示例使用
const template = "Hello, ${user.name}! Your balance is ${user.balance}. You have ${user.items[0]} in your cart. and ${user.items[2].kk}";
const exprObj = {
user: {
name: "Alice",
balance: 100.5,
items: ["Item1", "Item2", { kk: 1 }],
},
};
const compiledString = compile(template, exprObj);
console.log(compiledString);
解释:
- 首先使用正则表达式
/{{(.*?)}}/g将模板中的插值表达式{{...}}替换为 JavaScript 模板字符串语法${...}。 - 然后使用
new Function动态生成一个函数,该函数使用with语句将上下文对象作为作用域,返回替换后的模板字符串。 - 最后调用该函数并传入上下文对象,返回最终的编译结果。
需要注意的是,使用 with 函数虽然可以快速实现,但它会影响代码的性能和可读性,并且在严格模式下是不允许使用的。在实际开发中,建议使用方法一
用ES6声明一个class,在es5怎么实现
ES的实现方式
在ES5中没有class关键字,我们需要使用构造函数和原型链来模拟类和继承。
javascript
// 定义父类构造函数
function Parent(name, id) {
this.id id;
this.name = name;
this.list = ['a'];
this.printName = function() {
console.log(this.name);
};
}
// 在父类原型上添加方法
Parent.prototype.sayName = function() {
console.log(this.name);
};
//实现继承的辅助函数
function inheritPrototype(subClass, superClass) {
// 复制一份父类的原型
var p = Object.create(superClass.prototype);
// 修正构造函数
p.constructor = subClass;
// 设置子类原型
subClass.prototype = p;
}
// 定义子类构造函数
function Child(name, id) {
// 调用父类构造函数,绑定this到子类实例
Parent.call(this, name, id);
}
// 实现子类对父类的继承
inheritPrototype(Child, Parent);
代码解释
- 父类构造函数:在ES5中,我们使用普通的函数作为构造函数来模拟类的
constructor。在构造函数内部,我们可以为实例添加属性和方法。 - 原型方法:通过在构造函数的
prototype对象上添加方法,这些方法会被所有实例共享,类似于ES6中类的原型方法。 - 继承辅助函数:
inheritPrototype函数用于实现子类对父类的继承。它通过Object.create方法复制父类的原型,然后修正构造函数,最后将复制的原型赋值给子类的prototype。 - 子类构造函数:子类构造函数中使用
Parent.call(this, name, id)来调用父类的构造函数,确保父类的属性和方法被正确初始化。
分析结果
`const temp = {}
class Test { a(){} b = () => { } c = 1; d = {}; e = temp; static f = temp }
const test1 = new Test(); const test2 = new Test();
console.log(test1.a === test2.a)
console.log(test1.b === test2.b)
console.log(test1.c === test2.c)
console.log(test1.d === test2.d)
console.log(test1.e === test2.e)
console.log(test1 === test2)
console.log(Test.f === Test.f)
`
原因分析:
-
console.log(test1.a === test2.a); // truea是一个在Test类原型上定义的方法。由于test1和test2都是Test类的实例,它们会共享原型上的方法,因此test1.a和test2.a指向同一个函数。
-
console.log(test1.b === test2.b); // falseb是一个通过赋值创建的箭头函数,它是实例自己的属性,而不是原型上的属性。因此,test1.b和test2.b是两个不同的函数实例。
-
console.log(test1.c === test2.c); // truec是一个原始值(在这种情况下是数字),原始值是按值传递的,而不是按引用传递。但是,这里比较的是test1.c和test2.c的值,由于它们都被赋值为1,所以比较结果是true。
-
console.log(test1.d === test2.d); // falsed是一个对象,它是通过赋值创建的实例自己的属性。test1.d和test2.d是两个不同的对象实例。
-
console.log(test1.e === test2.e); // truee被赋值为同一个外部对象temp的引用。因此,test1.e和test2.e指向同一个对象。
-
console.log(test1 === test2); // falsetest1和test2是Test类的两个不同实例,因此它们不相等。
-
console.log(Test.f === Test.f); // true-
f是一个静态属性,它属于Test类本身,而不是类的实例。静态属性在类定义时只会被创建一次,因此Test.f指向的是同一个对象。
-
如果a对象引用了b对象,b对象引用了a对象怎么办,引用循环问题
问题分析
当对象之间存在循环引用时,进行深拷贝会陷入无限循环,因为深拷贝会不断尝试复制相互引用的对象。为了解决这个问题,需要使用一个缓存(cache)来记录已经拷贝过的对象,当再次遇到相同对象时,直接从缓存中获取,而不是再次进行拷贝。
代码实现
以下是一个解决循环引用问题的深拷贝函数示例:
javascript
function deepClone(obj, cache = new WeakMap()) {
// 如果 obj 不是对象,直接返回
if (typeof obj!== 'object' || obj === null) {
return obj;
}
// 检查缓存中是否已经存在该对象
if (cache.has(obj)) {
return cache.get(obj);
}
let clone;
if (Array.isArray(obj)) {
// 如果是数组,创建一个新数组
clone = [];
} else {
// 如果是普通对象,创建一个新对象
clone = {}
}
// 将当前对象存入缓存
cache.set(obj, clone);
// 遍历对象的属性
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
// 递归调用 deepClone 函数拷贝属性值
clone[key] = deepClone(obj[key], cache);
}
}
return clone;
}
// 测试循环引用
const a = {};
const b = {};
a.b = b;
b.a = a;
const clonedA = deepClone(a);
console.log(clonedA);
代码解释
- 缓存机制:使用
WeakMap作为缓存cache,它可以存储对象作为键,并且不会阻止对象被垃圾回收。 - 递归拷贝:在深拷贝过程中,递归调用
deepClone函数来处理对象的属性。 - 循环引用处理:在拷贝对象之前,先检查缓存中是否已经存在该对象。如果存在,直接从缓存中获取并返回;如果不存在,将该对象存入缓存,然后继续进行拷贝。
通过这种方式,可以避免循环引用导致的无限循环问题,实现正确的深拷贝。