1.如何使用JS判断对象是否存在循环引用?请写出具体代码
JS对象引用
let obj1 = {};let obj2 = {};obj1.reference = obj2; // obj1 引用 obj2obj2.reference = obj1; // obj2 引用 obj1
循环引用本身并不一定是问题,但在某些情况下,它可能会导致内存泄漏。这是因为垃圾回收器(Garbage Collector, GC)在检测对象是否应该被回收时,可能会因为循环引用而无法正确释放内存。
现在大多数现代JavaScript引擎(如V8引擎,用于Chrome和Node.js)使用标记-清除(Mark-and-Sweep)算法来管理内存。这种算法可以有效地处理循环引用。当垃圾回收器运行时,它会标记所有从根对象(通常是全局对象)可达的对象,然后清除未标记的对象。即使存在循环引用,只要这些对象不再从根对象可达,它们最终都会被清除。
注意事项
解除不必要的引用:在不需要时,手动解除对象之间的引用可以帮助垃圾回收器更快地回收内存。
使用弱引用:在ES6中,引入了
WeakMap
和WeakSet
,它们允许创建对对象的弱引用。这些引用不会阻止垃圾回收器回收对象。内存分析工具:使用Chrome开发者工具中的内存分析工具可以帮助你检测内存泄漏和不必要的循环引用。
Javascript深度遍历(递归与迭代[栈])与广度遍历 (队列)
在JavaScript中,要判断一个对象是否存在循环引用,你可以使用一种称为“深度遍历”或“深度搜索”的技术,同时跟踪你已经访问过的对象。如果在遍历过程中你遇到了一个已经访问过的对象,那么就说明存在循环引用。
下面是一个具体的代码示例,它使用了一个WeakSet
来跟踪已经访问过的对象,并递归地检查对象的属性来确定是否存在循环引用:
function hasCircularReference(obj, visited = new WeakSet()) { // 如果对象是null或undefined,或者已经访问过,则返回false if (!obj || visited.has(obj)) { return false; } // 将当前对象标记为已访问 visited.add(obj); // 遍历对象的所有可枚举属性 for (let key in obj) { if (obj.hasOwnProperty(key)) { let value = obj[key]; // 如果属性值是对象,则递归检查 if (typeof value === 'object' && value !== null) { // 如果在已访问的对象中找到了当前属性值,则说明存在循环引用 if (visited.has(value)) { return true; } // 递归检查属性值 if (hasCircularReference(value, visited)) { return true; } } } } // 如果没有找到循环引用,则返回false return false;
} // 示例用法
let obj1 = {};
let obj2 = {};
obj1.a = obj2;
obj2.b = obj1; console.log(hasCircularReference(obj1)); // 输出: true let obj3 = {};
console.log(hasCircularReference(obj3)); // 输出: false
- 函数定义:
- 定义一个名为
hasCircularReference
的函数,它接受两个参数:要检查的对象obj
和一个可选的visited
集合(默认为一个新的WeakSet
)。
- 定义一个名为
- 基础情况处理:
- 如果
obj
是null
或undefined
,或者已经存在于visited
集合中,则直接返回false
。这是因为null
和undefined
不是对象,不会形成循环引用,而已经访问过的对象则表明我们已经遍历过它的属性,无需再次检查。
- 如果
- 标记当前对象:
- 将当前对象
obj
添加到visited
集合中,表示它已被访问。
- 将当前对象
- 遍历对象属性:
- 使用
for...in
循环遍历对象的所有可枚举属性。这里需要注意的是,我们只检查对象自身的属性(使用hasOwnProperty
方法),而不包括从原型链上继承的属性。
- 使用
- 递归检查属性值:
- 对于每个属性,如果其值是对象(且不是
null
),则递归调用hasCircularReference
函数,将属性值作为新的对象传入,并传递当前的visited
集合。 - 如果在递归调用过程中发现属性值已经存在于
visited
集合中,则说明找到了循环引用,函数返回true
。
- 对于每个属性,如果其值是对象(且不是
- 完成遍历:
- 如果遍历完所有属性后都没有找到循环引用,则函数返回
false
。
- 如果遍历完所有属性后都没有找到循环引用,则函数返回
2.如何使用JS实现日期格式化函数?请写出具体代码
在JavaScript中,你可以使用内置的Date
对象以及自定义的函数来实现日期格式化。以下是一个简单的日期格式化函数的实现,它允许你将日期对象格式化为指定的字符串格式。
function formatDate(date, formatString) { const days = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']; const months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; const padZero = (num) => num.toString().padStart(2, '0'); const formatOptions = { 'YYYY': date.getFullYear(), 'MM': padZero(date.getMonth() + 1), // Months are zero based 'DD': padZero(date.getDate()), 'dddd': days[date.getDay()], 'MMM': months[date.getMonth()], 'HH': padZero(date.getHours()), 'mm': padZero(date.getMinutes()), 'ss': padZero(date.getSeconds()) }; return formatString.replace(/YYYY|MM|DD|dddd|MMM|HH|mm|ss/g, (match) => formatOptions[match]);
} // 使用示例
const now = new Date();
console.log(formatDate(now, 'YYYY-MM-DD HH:mm:ss')); // 输出类似 "2023-10-05 14:30:00"
console.log(formatDate(now, 'dddd, MMM DD, YYYY HH:mm:ss')); // 输出类似 "Thu, Oct 05, 2023 14:30:00"
代码解释
-
定义日期和月份的数组:
days
数组包含星期的缩写,months
数组包含月份的缩写。 -
辅助函数
padZero
:这个函数用于确保数字始终是两位数。如果数字小于10,它会在前面添加一个零。 -
定义
formatOptions
对象:这个对象包含要替换的格式字符串中的键和对应的日期值。注意,月份是从0开始的,所以date.getMonth() + 1
是正确的月份。 -
使用
replace
方法和正则表达式:formatString.replace(/YYYY|MM|DD|dddd|MMM|HH|mm|ss/g, (match) => formatOptions[match])
这行代码会查找formatString
中所有的格式占位符(如YYYY
、MM
等),并将它们替换为formatOptions
对象中对应的值。 -
使用示例:创建了一个
Date
对象now
,并使用formatDate
函数将其格式化为不同的字符串格式。
你可以根据需要扩展这个函数,添加更多的格式选项,比如毫秒(mss
)、时区信息等。这个基本的实现应该能满足大多数常见的日期格式化需求。
3.如何使用JS实现字符串的repeat方法?请写出具体代码
Javascript中Repeat方法
console.log('abc'.repeat(3)); // 输出 "abcabcabc" console.log('Hello'.repeat(2)); // 输出 "HelloHello" console.log(' '.repeat(4)); // 输出 " "(四个空格) console.log('A'.repeat(0)); // 输出 ""(空字符串) console.log('B'.repeat(-1)); // 输出 ""(空字符串) console.log('C'.repeat(2.5)); // 输出 "CC"(2.5 被截断为 2)
在JavaScript中,String.prototype.repeat()
方法用于将字符串重复指定的次数,并返回一个新的字符串。但是,如果你想要自己实现一个类似的方法,你可以编写一个函数来模拟这个行为。
以下是一个简单的实现,名为 myRepeat
,它接受两个参数:要重复的字符串 str
和重复的次数 count
,并返回重复后的新字符串:
function myRepeat(str, count) { // 如果 count 不是正整数,则返回空字符串 if (count < 1 || !Number.isInteger(count)) { return ''; } // 使用空字符串作为累加器 let result = ''; // 使用循环将字符串重复 count 次 for (let i = 0; i < count; i++) { result += str; } // 返回重复后的字符串 return result;
} // 使用示例
console.log(myRepeat('abc', 3)); // 输出 "abcabcabc"
console.log(myRepeat('Hello, ', 2)); // 输出 "Hello, Hello, "
console.log(myRepeat('A', 0)); // 输出 ""
console.log(myRepeat('B', -1)); // 输出 ""
console.log(myRepeat('C', 2.5)); // 输出 "",因为2.5不是整数
4.如何使用JS实现字符串翻转?请写出具体代码
在JavaScript中,你可以通过多种方式实现字符串的翻转。以下是一个简单的实现方法,它使用了JavaScript的数组功能来轻松地反转字符串:
function reverseString(str) { // 将字符串转换为数组(因为字符串在JavaScript中是不可变的) let strArray = str.split(''); // 使用数组的reverse方法反转数组中的元素顺序 strArray.reverse(); // 使用数组的join方法将反转后的数组元素重新组合成字符串 let reversedStr = strArray.join(''); // 返回翻转后的字符串 return reversedStr;
} // 使用示例
console.log(reverseString('hello')); // 输出 'olleh'
console.log(reverseString('world')); // 输出 'dlrow'
console.log(reverseString('JavaScript')); // 输出 'tpircSavaJ'
代码解释
-
字符串转换为数组:
str.split('')
将字符串str
分割成一个字符数组strArray
。每个字符现在都是数组中的一个元素。 -
数组反转:
strArray.reverse()
方法会原地(in-place)反转数组中元素的顺序。这意味着strArray
本身会被修改,其元素顺序将变为相反的顺序。 -
数组转换回字符串:
strArray.join('')
方法将strArray
中的元素重新组合成一个单一的字符串reversedStr
。因为我们在join
方法中传递了空字符串''
作为分隔符,所以数组中的元素会被直接连接在一起,没有额外的字符或空格。 -
返回结果:最后,函数返回翻转后的字符串
reversedStr
。
这种方法利用了JavaScript数组的强大功能,使得字符串翻转变得简单且易于理解。当然,还有其他方法可以实现字符串翻转,比如使用循环或递归,但上面的方法通常是最简洁和高效的。
//reduce方法
function reverseStringReduce(str) { return str.split('').reduce((reversed, char) => char + reversed, '');
} // 使用示例
console.log(reverseStringReduce('hello')); // 输出: 'olleh'
---------------------------------------------------------------------------
//for循环
function reverseStringLoop(str) { let reversed = ''; for (let i = str.length - 1; i >= 0; i--) { reversed += str[i]; } return reversed;
} // 使用示例
console.log(reverseStringLoop('hello')); // 输出: 'olleh'
5.如何用JS实现将数字每干分位用逗号隔开(1000转为1,000)?请写出具体代码
步骤:
1)将数字转换为字符串,以便使用字符串方法进行处理。
2)使用正则表达式匹配字符串中的位置,在每三个数字前插入一个逗号。
3)返回格式化后的字符串。
function formatNumberWithCommasCustom(number) { // 将数字转换为字符串,并去掉可能的小数点 let str = Math.floor(number).toString(); // 初始化结果字符串和一个计数器 let result = ''; let count = 0; // 从字符串的最后一个字符开始遍历 for (let i = str.length - 1; i >= 0; i--) { // 将当前字符添加到结果字符串的前面 result = str[i] + result; // 每添加一个字符,计数器加1 count++; // 如果计数器达到3(意味着已经添加了3个字符),则插入一个逗号,并重置计数器 if (count === 3 && i !== 0) { result = ',' + result; count = 0; } } // 如果原始数字有小数部分,则将其添加到结果字符串的后面 if (number % 1 !== 0) { result += '.' + (number - Math.floor(number)).toFixed(2).slice(2); // 保留两位小数 } return result;
} // 使用示例(注意:自定义函数在处理小数时可能不如toLocaleString()准确)
console.log(formatNumberWithCommasCustom(1000)); // 输出: "1,000"
console.log(formatNumberWithCommasCustom(1234567)); // 输出: "1,234,567"
console.log(formatNumberWithCommasCustom(1234567.89)); // 输出可能是 "1,234,567.89"(但这种方法在处理小数时稍显笨拙)
6.如何使用JS + HTML 实现图片懒加载?请写出具体代码
图片懒加载
一、定义与原理
图片懒加载,也称为延迟加载,是指在单页面应用中,当用户滚动页面到图片位置时才加载图片的一种技术。其原理主要是利用JavaScript或CSS来判断图片是否进入用户的可视区域,从而决定是否加载图片。当用户滚动页面时,通过监听滚动事件,可以判断哪些图片进入了可视区域,并将这些图片的src属性替换为真实的图片地址,从而实现图片的加载。
二、实现方式
图片懒加载的实现方式有多种,以下是几种常见的实现方法:
- 设置img标签的loading属性:
- 在HTML中,可以直接为img标签设置loading="lazy"属性,这样浏览器就会延迟加载屏幕外的图像,直到用户滚动到它们附近。
- 这种方法简单且兼容性好,是现代浏览器推荐的实现方式。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Lazy Loading Example</title> </head> <body> <img src="placeholder.jpg" data-src="real-image.jpg" alt="Example Image" loading="lazy"> <!-- 其他内容 --> </body> </html>
- 使用JavaScript监听滚动事件:
- 通过JavaScript监听页面的滚动事件,可以判断哪些图片进入了可视区域。
- 一旦图片进入可视区域,就将其src属性替换为真实的图片地址。
- 这种方法需要手动计算图片的位置和视口的高度,并且滚动事件可能频繁触发,因此需要注意性能优化。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Lazy Loading Example</title> <style> .lazy { display: block; width: 100%; height: 200px; /* 设置占位高度 */ background: #f0f0f0; /* 占位背景色 */ } </style> </head> <body> <img class="lazy" data-src="real-image1.jpg" alt="Example Image 1"> <img class="lazy" data-src="real-image2.jpg" alt="Example Image 2"> <!-- 其他内容 --> <script> document.addEventListener('DOMContentLoaded', function() { let lazyImages = [].slice.call(document.querySelectorAll('img.lazy')); let active = false; const lazyLoad = function() { if (active === false) { active = true; setTimeout(function() { lazyImages.forEach(function(lazyImage) { if ((lazyImage.getBoundingClientRect().top <= window.innerHeight && lazyImage.getBoundingClientRect().bottom >= 0) && getComputedStyle(lazyImage).display !== 'none') { lazyImage.src = lazyImage.dataset.src; lazyImage.classList.remove('lazy'); lazyImages = lazyImages.filter(function(image) { return image !== lazyImage; }); if (lazyImages.length === 0) { document.removeEventListener('scroll', lazyLoad); window.removeEventListener('resize', lazyLoad); window.removeEventListener('orientationChange', lazyLoad); } } }); active = false; }, 200); } }; document.addEventListener('scroll', lazyLoad); window.addEventListener('resize', lazyLoad); window.addEventListener('orientationChange', lazyLoad); }); </script> </body> </html>
- 使用IntersectionObserver API:
- IntersectionObserver是浏览器提供的一个API,用于异步观察目标元素与其祖先元素或顶级文档的视口相交情况的变化。
- 通过这个API,可以轻松地判断图片是否进入视口,并在进入视口时加载图片。
- 这种方法性能更好,且不需要手动计算元素位置。但需要注意的是,它可能不兼容旧的浏览器版本。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Lazy Loading Example</title> <style> .lazy { display: block; width: 100%; height: 200px; /* 设置占位高度 */ background: #f0f0f0; /* 占位背景色 */ } </style> </head> <body> <img class="lazy" data-src="real-image1.jpg" alt="Example Image 1"> <img class="lazy" data-src="real-image2.jpg" alt="Example Image 2"> <!-- 其他内容 --> <script> document.addEventListener('DOMContentLoaded', function() { let lazyImages = document.querySelectorAll('img.lazy'); let lazyImageObserver = new IntersectionObserver(function(entries, observer) { entries.forEach(function(entry) { if (entry.isIntersecting) { //视口目标对象let lazyImage = entry.target; //获取图片路径lazyImage.src = lazyImage.dataset.src; lazyImage.classList.remove('lazy'); //移除观察lazyImageObserver.unobserve(lazyImage); } }); }); lazyImages.forEach(function(lazyImage) { lazyImageObserver.observe(lazyImage); }); }); </script> </body> </html> ————————————————————————————————————————————————————————————————————————————————————————— // 创建一个IntersectionObserver实例 const observer = new IntersectionObserver((entries, observer) => { entries.forEach(entry => { if (entry.isIntersecting) { console.log('进入视窗'); // 在此处执行相关操作,如加载内容等 } else { console.log('离开视窗'); } }); }, { root: null, // 使用视窗作为根元素 rootMargin: '0px', threshold: [0, 0.25, 0.5, 0.75, 1] // 当目标元素0%、25%、50%、75%、100%可见时触发回调函数 }); // 获取需要观察的元素 const targetElement = document.querySelector('#target'); // 开始观察该元素 observer.observe(targetElement);
三、优点与缺点
优点:
- 提高页面加载速度:通过延迟加载非可视区域的图片,可以减少页面的初始加载时间。
- 节省用户流量:用户只加载他们实际看到的图片,从而节省了流量。
- 提升用户体验:对于网络条件较差的用户或移动设备用户来说,懒加载可以显著地提高用户体验。
缺点:
- 兼容性问题:虽然现代浏览器大多支持懒加载技术,但仍有一些旧版本的浏览器可能不兼容。
- 实现复杂度:使用JavaScript监听滚动事件或IntersectionObserver API实现懒加载需要一定的编程技能
7.如何使用JS实现非负大整数的相加?请写出具体代码
在JavaScript中,由于Number
类型只能安全地表示到 ±9007199254740991(2^53-1)
(即 Number.MAX_SAFE_INTEGER
)的整数,对于超过这个范围的非负大整数相加,我们需要使用其他方法。
一种常见的方法是使用字符串来表示这些大整数,并编写一个函数来模拟大整数的相加过程。以下是一个实现非负大整数相加的JavaScript代码示例:
function addLargeNumbers(num1, num2) { // 去除字符串两端的空格,并反转字符串以便从低位开始相加[key1]let str1 = num1.trim().split('').reverse().join(''); let str2 = num2.trim().split('').reverse().join(''); let maxLength = Math.max(str1.length, str2.length); let result = ''; let carry = 0; // 进位 // 逐位相加 for (let i = 0; i < maxLength; i++) { let digit1 = i < str1.length ? parseInt(str1[i]) : 0; let digit2 = i < str2.length ? parseInt(str2[i]) : 0; let sum = digit1 + digit2 + carry;//[key2] result += sum % 10; // 当前位的值 carry = Math.floor(sum / 10); // 进位 } // 如果最后还有进位,需要加到结果的最前面[长度相同时,key3]if (carry > 0) { result = carry.toString() + result; } // 反转结果字符串,得到最终的大整数相加结果 return result.split('').reverse().join('');
} // 测试
let num1 = "12345678901234567890";
let num2 = "98765432109876543210";
console.log(addLargeNumbers(num1, num2)); // 输出 "111111111011111111100"
--------------------------------------------------------------------------------------
function addLargeNumbersWithBigInt(num1, num2) { // 将字符串转换为BigInt let bigInt1 = BigInt(num1); let bigInt2 = BigInt(num2); // 进行相加运算 let result = bigInt1 + bigInt2; // 将结果转换回字符串(如果需要) return result.toString();
} // 测试
let num1 = "12345678901234567890";
let num2 = "98765432109876543210";
console.log(addLargeNumbersWithBigInt(num1, num2)); // 输出 "111111111011111111100"