欢迎来到《前端面试宝典》,这里是你通往互联网大厂的专属通道,专为渴望在前端领域大放异彩的你量身定制。通过本专栏的学习,无论是一线大厂还是初创企业的面试,都能自信满满地展现你的实力。
核心特色:
- 独家实战案例:每一期专栏都将深入剖析真实的前端面试案例,从基础知识到前沿技术,从算法挑战到框架运用,让你在实战中迅速成长。
- 深度技术解析:不再只是表面文章,我们将带你深入技术的核心,理解每一个知识点背后的原理与- - 应用场景,让你在面试中不仅知其然,更知其所以然。
- 高质量内容保证:每一期内容都经过精心策划与打磨,确保信息的准确性和实用性,拒绝泛泛而谈,只提供真正有价值的内容。
- 持续更新迭代:持续关注前端领域的最新动态,及时更新专栏内容,确保你始终站在技术的最前沿。
7. 异步加载JS脚本的方式有哪些?
异步加载JavaScript脚本是现代Web开发中常用的技术,它有助于改善页面加载性能,避免阻塞用户界面。以下是一些常用的异步加载JS脚本的方式:
- 使用
<script>
标签的async属性: 当async属性被设置时,脚本会被异步下载和执行,不会阻塞页面的渲染。脚本的执行顺序不受控制,即脚本可能在页面DOM构建完成之前或之后执行。
<script src="script.js" async></script>
- 使用
<script>
标签的defer属性: defer属性同样允许脚本异步下载,但它保证脚本会在文档解析完成后,DOMContentLoaded事件触发前按照脚本在文档中的顺序执行。
<script src="script.js" defer></script>
- 使用fetch或XMLHttpRequest: 可以使用HTTP请求异步获取远程脚本的内容,然后通过eval或new Function执行脚本,但这两种方法都有安全风险,不推荐使用。
fetch('script.js')
.then(response => response.text())
.then(script => eval(script)); // 不推荐使用eval
- 使用importScripts: 这个方法主要用于Web Workers中,用于异步加载脚本。
importScripts('workerScript.js');
- 使用
<iframe>
加载脚本: 将脚本嵌入在一个隐藏的<iframe>
中,然后通过访问iframe.contentWindow或iframe.contentDocument来执行脚本。不过,这种方式可能受到同源策略的限制。
<iframe srcdoc="<script>console.log('Script loaded');</script>" style="display:none;"></iframe>
- 使用模块加载器如Webpack、RequireJS、import()等: 这些模块加载器支持异步加载模块,可以配置为按需加载脚本,通常用于大型项目中。
<script type="module">
import('./index.js').then((myModule) => {
myModule.default(); // 假设index.js导出了一个默认函数
}).catch(error => {
console.error('Failed to load module:', error);
});
</script>
- 使用动态创建
<script>
标签: 在运行时动态创建<script>
标签并插入到DOM中,可以控制脚本的加载时机。
const script = document.createElement('script');
script.src = 'script.js';
script.onload = () => {
// 脚本加载完成后的回调
};
document.head.appendChild(script);
8. 解释一下JavaScript中的变量提升(Hoisting)
在JavaScript中,变量提升(Hoisting)是一种特殊的变量和函数声明行为。在代码执行之前,JavaScript解释器会先进行预解析(parsing)过程,这个过程中会把变量和函数声明移动到它们所在作用域的顶部。这意味着在变量或函数被实际声明之前,你就可以引用它们。但是需要注意的是,只有声明会被提升,而初始化或赋值操作则不会。
变量提升的规则:
-
- 变量声明会被提升: 如果你使用var、let或const声明变量,声明语句会被提升到作用域的顶部。但是,let和const声明的变量在它们的声明之前处于“暂时性死区”(TDZ,Temporal Dead Zone),在这个区域内访问它们会抛出引用错误。
-
- 函数声明会被完全提升: 函数不仅声明会被提升,而且函数体也会被提升,这意味着你可以在函数声明之前调用函数。
-
- 函数表达式只会提升声明: 如果你使用函数表达式(即函数赋值给一个变量),只有变量声明会被提升,函数体不会被提升。因此,在函数表达式被赋值之前尝试调用它会导致undefined引用错误。
示例:
console.log(x); // 输出: undefined
var x = 5;
console.log(y); // 输出: ReferenceError: Cannot access 'y' before initialization
let y = 5;
console.log(z); // 输出: ReferenceError: z is not defined
const z = 5;
console.log(func1()); // 输出: "Hello from func1"
function func1() {
return "Hello from func1";
}
console.log(func2()); // 输出: TypeError: func2 is not a function
var func2 = function() {
return "Hello from func2";
};
在上面的示例中,可以看到var x的声明被提升到了顶部,但是初始化没有提升,所以x在初始化之前是undefined。let y和const z的声明虽然也被提升,但是在声明之前访问它们会抛出错误,因为它们处于暂时性死区。函数func1的声明和函数体都被提升,所以可以直接调用。而func2的声明被提升,但是函数体没有提升,所以在初始化之前调用它会失败。
变量提升是JavaScript的一个重要特性,理解它对于避免一些常见的编程陷阱非常重要。在编写代码时,最好遵循良好的编程习惯,如在作用域的顶部声明变量,以减少因变量提升带来的潜在问题。
9. let、const、var 的区别有哪些?
在JavaScript中,var, let, 和 const 是用于声明变量的关键词,它们之间有几个重要的区别:
- 变量提升(Hoisting)
var: 定义的变量会被提升至作用域的顶部,也就是说,你可以在声明之前使用变量,尽管这通常会导致undefined的值。
let 和 const: 不会发生变量提升,你必须在使用变量之前声明它们。尝试在声明之前访问它们会导致ReferenceError,这是因为它们处于暂时性死区(TDZ)。 - 重新声明
var: 允许在同一作用域内多次声明同一个变量,后面的声明会覆盖前面的声明。
let 和 const: 不允许在相同的作用域内重复声明同一个变量,这样做会导致SyntaxError。 - 作用域
var: 具有函数作用域,这意味着在函数内部声明的变量在整个函数范围内都是可见的。如果在全局作用域中声明,它会成为全局变量。
let 和 const: 具有块级作用域,这意味着在代码块(如if语句或循环)中声明的变量只在该代码块内可见。 - 可变性
var 和 let: 宣告的变量是可变的,可以重新赋值。
const: 宣告的是常量,一旦初始化后不能重新赋值。但是,如果const变量指向一个对象或数组,那么对象或数组的内容是可以修改的,只是变量本身不能指向一个新的对象或数组。
举例说明
// 变量提升
console.log(x); // 输出 undefined
var x = 5;
// 暂时性死区
console.log(y); // 抛出 ReferenceError
let y = 5;
// 重新声明
var z = 10;
var z = 20; // z现在等于20
let a = 10;
let a = 20; // 抛出 SyntaxError
const b = 10;
const b = 20; // 抛出 SyntaxError
// 作用域
if (true) {
var c = 10;
}
console.log(c); // 输出 10
if (true) {
let d = 10;
}
console.log(d); // 抛出 ReferenceError
// 可变性
let e = [1, 2, 3];
e.push(4); // 数组内容改变,e仍然是原来的数组
const f = [1, 2, 3];
f.push(4); // 数组内容改变,f仍然是原来的数组
f = [5, 6]; // 抛出 TypeError
总结来说,var主要用于旧的JavaScript代码中,而let和const则在ES6及以后的代码中更为常见,它们提供了更安全和灵活的变量管理方式。在现代JavaScript开发中,推荐使用let和const。