昨天,咱们介绍了关于Javascript面试篇的基础篇。今天,让我们来看看进阶篇都有什么。
12. 闭包是什么?闭包的使用场景有哪些?
闭包 是一种特性,它使得函数能够捕获其定义时的环境(或者说保留对外部作用域变量的访问),即便该作用域已经关闭。可以说,闭包是函数与其定义时词法环境的组合体。
换句话说,闭包赋予函数访问自身作用域、外部函数作用域以及全局作用域的能力,使它能“记住”并持续访问这些作用域中的变量和参数。
function 外部函数() {
let 外部变量 = '我来自外部函数';
return 内部函数() {
console.log(外部变量); // 从外部函数作用域访问外部变量
}
}
let 我的函数 = 外部函数();
我的函数(); // 输出: 我来自外部函数
每当创建一个函数时,特别是在一个函数内部定义另一个函数时,就会产生闭包。
执行上下文是JavaScript代码执行的环境。每次函数调用时,都会创建一个独立的执行上下文并压入执行栈。一旦函数执行完成,它就会从栈中弹出。
每个执行上下文都有一个内存空间来存储其变量和函数,一旦函数从执行栈中弹出,JavaScript垃圾回收器会清理所有这些内容。
在JavaScript中,只有当没有引用指向某对象时,该对象才会被垃圾回收。
在上述例子中,即使外部函数()
已完成执行,匿名执行上下文仍然保持着对外部环境变量的引用。(它能够访问外部变量
并在console.log中使用)。
闭包在JavaScript中有几个重要的使用场景:
- 数据隐私与封装:闭包可用于创建私有数据并封装功能在有限的作用域内。通过在另一个函数内部定义函数,内部函数可以访问外部函数的变量,而这些变量对函数外部不可见。这使得可以创建不直接从外部访问的私有数据和方法,从而增强数据隐私和封装性。
- 状态保持:在处理异步操作和事件处理时,闭包常用于保持状态。例如,在处理异步任务时,闭包可以捕获并保留跨多个异步操作的状态变量,确保在异步任务完成时访问到正确的变量。
- 柯里化和偏函数应用:闭包便于实现函数式编程技术,如柯里化和偏函数应用。通过使用闭包捕获并记住特定参数,然后返回使用这些被捕获参数的新函数,可以实现柯里化和偏函数应用。这允许创建具有预设参数的特化函数,提供灵活性和可重用性。
- 模块模式:闭包在实现JavaScript的模块模式中至关重要。通过使用闭包创建私有变量并仅公开必要的公共方法,开发者可以创建模块化且组织良好的代码,防止对内部模块数据的非授权访问和修改。
- 回调函数:在处理回调函数时,经常使用闭包。闭包可以用来捕获并维护异步操作上下文中变量的状态,确保在回调函数被调用时能访问到正确的变量。
13. 解释JavaScript中的提升(Hoisting)概念。
JavaScript中的提升是指在编译阶段,变量和函数声明会被移动到它们所在作用域的顶部的默认行为。这意味着你可以在代码中声明之前使用变量或调用函数。
使用var
声明的变量,其声明会被提升至包含它的函数或块的顶部,并初始化为默认值“undefined”。
console.log(x); // 输出: undefined
var x = 5;
使用let
和const
声明的变量也会被提升,但存在一个“暂时性死区”(TDZ),在此期间它们无法被访问。
console.log(x); // 抛出错误(ReferenceError)
let x = 5;
函数声明同样会被提升至其包含作用域的顶部。你可以在代码中函数声明之前调用函数。
sayHello(); // 输出: "Hello, world!"
function sayHello() {
console.log("Hello, world!");
}
箭头函数、函数表达式或变量初始化不会发生提升。
14. 什么是暂时性死区(Temporal dead zone, TDZ)?
暂时性死区(Temporal Dead Zone, TDZ)是JavaScript中与使用let
和const
声明变量相关的一个概念。
当你使用let
或const
声明变量时,它们会被提升至所在作用域的顶部,但是与var
不同的是,使用let
和const
声明的变量在TDZ中保持未初始化状态。
在实际声明之前尝试访问或使用该变量,会导致ReferenceError
。这是为了防止在变量被正确定义之前就使用它们,以减少潜在的错误。
理解暂时性死区对于预防与变量使用前未初始化相关的错误非常重要。它还促进了遵循最佳实践的JavaScript编码方式,鼓励在使用前正确声明变量。
15. 什么是原型链? 及 Object.create()
方法?
在JavaScript中,每个函数和对象默认都拥有一个名为prototype
的属性。每个对象都具备一个原型,这个原型是从当前对象继承属性和方法的另一个对象。你可以把原型想象成一个模板或父对象。
原型链是一种机制,它使得对象能够从其他对象那里继承属性和方法。
当你访问一个对象上的属性或方法时,JavaScript首先会在该对象本身查找。如果找不到,它就会向上遍历原型链,直到找到该属性或方法。这一过程会持续进行,直到到达原型链顶端的Object.prototype
。
16. call()
, apply()
, 和 bind()
方法有什么区别?
call()
: call()
方法会使用指定的this
值和作为逗号分隔值传递的单独参数来调用一个函数。
const person1 = { name: 'John' };
const person2 = { name: 'Jane' };
function greet(greeting) {
console.log(greeting + ' ' + this.name);
}
greet.call(person1, 'Hello'); // 输出: Hello John
greet.call(person2, 'Hi'); // 输出: Hi Jane
// 使用call()方法,一个对象可以借用另一个对象的方法。
const o1 = {
name: 'ravi',
getName: function() {
console.log(`Hello, ${this.name}`);
}
};
const o2 = {
name: 'JavaScript Centric'
};
o1.getName.call(o2); // Hello, JavaScript Centric
apply()
: 调用函数时使用指定的this
值,但它接受一个数组作为参数。当传递的参数数量未知或者参数已经在一个数组中时,此方法特别有用。
const numbers = [1, 2, 3, 4, 5];
const max = Math.max.apply(null, numbers);
console.log(max); // 输出: 5
bind()
: 而不是立即调用,它返回一个新的函数,并允许你传递任意数量的参数。bind()
方法接受一个对象作为第一个参数,并创建一个新的函数。
const module = {
x: 42,
getX: function() {
return this.x;
}
};
const boundGetX = module.getX.bind(module);
console.log(boundGetX()); // 输出: 42
17. 什么是lambda或箭头函数?
JavaScript中有两种类型的函数:
- 常规函数
- 箭头函数(在ES6中引入)
常规函数: 我们可以用两种方式编写常规函数,即函数声明和函数表达式。
箭头函数(也称为胖箭头函数): 是ES6中引入的一种特性,它是编写函数表达式的更简洁语法。与传统函数表达式相比,它们的语法更短,特别适合于创建匿名函数和使用函数式编程概念。
箭头函数没有声明方式,只能通过函数表达式来定义。
箭头函数与常规函数之间存在一些差异,包括:
- 语法
- 不支持
arguments
(参数为类数组对象) - 箭头函数没有自己的原型对象
- 不能使用
new
关键字调用(不是构造函数) - 没有自己的
this
(call
,apply
, 和bind
不按预期工作) - 不能作为生成器函数使用
- 不允许参数名称重复
18. 什么是柯里化函数?
柯里化是一种技术,在函数式编程中,它将一个接收多个参数的函数转换为一系列只接收单个参数的函数。这些柯里化后的函数可以组合起来构建更复杂的函数。
在JavaScript中,你可以通过使用闭包和返回函数来实现柯里化。
// 接收两个参数的常规函数
function add(x, y) {
return x + y;
}
// 柯里化版本的函数
function curryAdd(x) {
return function(y) {
return x + y;
};
}
const add5 = curryAdd(5); // 部分应用,创建新函数
console.log(add5(3)); // 输出: 8
柯里化在函数式编程中非常有用,可以使代码更加模块化和可重用。它特别适用于想要创建具有可变数量参数的函数或构建数据转换管道的场景。
19. ES6有哪些特点?
ES6,也称为ECMAScript 2015,向JavaScript引入了许多新特性和改进,显著扩展了语言的功能。ES6的一些关键特性包括:
- 箭头函数
- 块级作用域变量
- 类
- 模块
- 模板字面量:允许使用反引号嵌入表达式和多行字符串,提供了创建复杂字符串的更便捷方式。
- 默认参数
- 剩余与展开运算符
- 解构赋值
- Promise
- Map, Set, WeakMap, WeakSet:ES6引入了新的内置数据结构,如Map和Set,以便更高效地处理集合和键值对。
- 迭代器和生成器
- 增强的对象字面量