前端面试笔记

前端面试笔记

liohi

Javascipt考点

闭包(Closures)

解释闭包的概念、原理和用途。了解闭包如何捕获外部变量以及可能导致的内存泄漏问题。

闭包的概念: 闭包是指一个函数能够记住并访问它被创建时所处的词法作用域,即使该函数在不同的作用域中被调用,也能够访问这个词法作用域中的变量。换句话说,闭包允许函数在其声明时的作用域之外被调用,但仍然可以访问其外部作用域的变量。

闭包的原理: 当一个函数被定义时,它会捕获所在作用域的变量,将这些变量保存在自己的闭包中。这意味着在函数内部可以访问外部作用域中的变量。当函数返回后,它的闭包仍然保持对外部作用域的引用,因此这些变量不会被垃圾回收机制清除,直到闭包不再被引用。

闭包的用途: 闭包在JavaScript中有许多有用的用途,包括:

  1. 数据封装和私有性: 通过闭包,可以创建私有变量和方法,将数据隐藏在函数作用域内,只通过暴露的接口访问,实现了信息的封装和隐藏。
  2. 模块化和命名空间: 通过闭包可以创建模块,将相关的功能封装在一个函数内部,避免命名冲突和全局变量污染。
  3. 回调函数: 在异步编程中,闭包可以用作回调函数,捕获外部作用域的状态,使得回调函数能够访问正确的数据。
  4. 高阶函数: 闭包可以用于创建高阶函数,即接受一个或多个函数作为参数或返回一个函数的函数。

闭包捕获外部变量: 闭包捕获了它被创建时所处的词法作用域中的变量。这意味着函数内部引用的外部变量,即使函数在其他地方被调用,它也能够访问这些变量。

内存泄漏问题: 闭包可以导致内存泄漏问题,特别是在不正确使用闭包的情况下。如果闭包长时间持有对外部作用域中大量变量的引用,这些变量将不会被垃圾回收,从而导致内存泄漏。为避免内存泄漏,需要在不再需要闭包时,手动解除对外部变量的引用,或者使用适当的垃圾回收策略。

闭包垃圾回收策略

闭包内存泄漏是由于闭包持有对外部变量的引用,导致这些变量无法被垃圾回收,从而造成内存泄漏。下面是一些解决闭包内存泄漏的方法:

1. 避免不必要的闭包: 尽量避免在不需要的情况下创建闭包。只有当确实需要保留状态或行为时,才应使用闭包。

2. 及时解除引用: 在不再需要闭包时,手动将闭包引用的外部变量赋值为null,从而解除对外部变量的引用,使其能够被垃圾回收。

1
2
3
4
5
6
7
8
9
10
11
12
13
function createClosure() {
var data = "Some data";

// 创建闭包
var closure = function() {
console.log(data);
};

// 在适当的时机解除引用
closure = null;

return closure;
}

3. 使用事件委托: 当处理事件监听器时,使用事件委托来避免每个元素都有一个闭包。将事件监听器绑定到父元素,通过事件冒泡捕获事件,这样只会创建一个闭包。

4. 使用块级作用域: 使用块级作用域(letconst)代替函数作用域(var),因为块级作用域在离开块后会自动解除引用。

5. 将闭包变量设为不可配置: 使用Object.defineProperty将闭包中的变量设置为不可配置、不可写,这样在某些情况下会帮助解除引用。

6. 使用模块化: 使用模块化的方式来组织代码,限制闭包的作用域,避免不必要的变量引用。

7. 使用垃圾回收工具: 使用现代的开发工具和调试工具来检测和诊断内存泄漏,例如 Chrome DevTools 的 Memory 面板。

作用域和作用域链(Scope and Scope Chain)

解释词法作用域、函数作用域、块级作用域和变量提升。描述作用域链的概念,以及变量查找的顺序。

词法作用域(Lexical Scope):
词法作用域是指变量的可见性和访问规则是在代码编写阶段就确定的,而不是在运行时确定的。JavaScript使用词法作用域来决定在何处查找变量。

函数作用域:
函数作用域是指变量的可见性仅限于函数内部。在函数内部定义的变量对函数外部是不可见的,但函数内部可以访问外部作用域中的变量。

块级作用域:
块级作用域是指变量的可见性仅限于包含变量声明的代码块内部(通常是由花括号 {} 包围的部分)。在ES6之前,JavaScript没有块级作用域,但通过var声明的变量会在整个函数作用域中生效。

变量提升:
变量提升是指在代码执行前,JavaScript引擎会将变量的声明部分提升到作用域的顶部,但不会提升变量的赋值部分。这意味着你可以在变量声明之前访问变量,但值会是undefined

作用域链的概念:
作用域链是指由多个嵌套作用域构成的链式结构。每个作用域都保持一个对其父级作用域的引用,形成了一个作用域链。当访问一个变量时,JavaScript引擎首先在当前作用域中查找,如果找不到,它会向上一级作用域查找,依此类推,直到找到变量或到达全局作用域。

变量查找的顺序:
当访问一个变量时,JavaScript引擎会按照以下顺序查找:

  1. 首先,在当前作用域中查找变量。
  2. 如果在当前作用域中找不到变量,则向上一级作用域查找,依此类推,直到找到变量或到达全局作用域。
  3. 如果在全局作用域中仍然找不到变量,JavaScript会抛出一个ReferenceError错误。

下面是一个示例,演示了词法作用域、函数作用域、块级作用域和变量提升的概念:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
var globalVar = "Global"; // 全局作用域

function foo() {
var functionVar = "Function Scope"; // 函数作用域

if (true) {
var blockVar = "Block Scope"; // 块级作用域(在ES6之前没有)
}

console.log(globalVar); // 可访问
console.log(functionVar); // 可访问
console.log(blockVar); // 可访问(在ES6之前)
}

foo();

console.log(globalVar); // 可访问
console.log(functionVar); // 不可访问
console.log(blockVar); // 不可访问

原型和原型链(Prototype and Prototype Chain)

详细介绍原型、构造函数、原型链和继承。解释原型继承和使用prototype__proto__的区别。

原型(Prototype):

原型是JavaScript中对象之间继承关系的基础。每个JavaScript对象都有一个原型对象,它包含了一些共享的属性和方法。对象可以通过原型链来访问原型对象中的属性和方法。原型关系是通过引用来建立的。

构造函数(Constructor):
构造函数是用于创建对象的函数,通常以大写字母开头,例如Person。当通过构造函数使用new关键字创建一个对象实例时,实例会自动继承构造函数的原型对象上的属性和方法。

原型链(Prototype Chain):
原型链是由一系列对象及其原型对象所组成的链式结构,用于属性和方法的查找。当访问一个对象的属性或方法时,JavaScript会从对象自身开始查找,如果找不到,则沿着原型链向上查找,直到找到属性或方法或者到达原型链的顶端(Object.prototype)。

继承:
继承是指一个对象可以获得另一个对象的属性和方法,以实现代码的重用和组织。在JavaScript中,继承通过原型链来实现。子对象可以通过原型链访问父对象的属性和方法,实现了继承关系。

原型继承:
原型继承是一种基于原型链的继承方式。一个对象可以通过将另一个对象设置为其原型,从而继承另一个对象的属性和方法。这样,子对象可以通过原型链访问父对象的属性和方法。

使用prototype__proto__的区别:

  • prototype:每个函数都有一个prototype属性,它指向一个对象,这个对象会成为通过该构造函数创建的实例的原型。通过在构造函数上设置原型属性,可以添加共享的属性和方法。这种方式用于定义实例的原型,实现方法的共享。

  • __proto____proto__是每个对象都具有的属性,它指向对象的原型。通过__proto__可以访问和修改对象的原型。但是,应该尽量避免直接使用__proto__,因为它是非标准的属性,在某些情况下可能不受支持。

综上所述,原型、构造函数、原型链和继承是JavaScript中重要的概念,它们共同构成了对象之间的关系和继承机制。通过使用构造函数和原型,可以实现对象的属性和方法的共享和继承,从而使代码更加模块化和可维护。

this关键字

解释this在不同情境下的指向,包括普通函数、箭头函数、构造函数、事件处理函数等。理解this的动态绑定机制。

this 是 JavaScript 中一个非常重要的关键字,它指向当前函数执行的上下文对象。this 的指向在不同的情境下有不同的行为,下面解释了在不同情境下 this 的指向:

  1. 普通函数:
    在普通函数中,this 的指向取决于函数的调用方式。如果函数是作为普通函数调用,this 指向全局对象(在浏览器环境中是 window 对象)。但是在严格模式下,this 指向 undefined

  2. 箭头函数:
    箭头函数中的 this 是词法作用域中的 this,它不会被动态绑定,而是在函数定义时就确定了。箭头函数的 this 始终指向最近一层非箭头函数的上下文。这意味着箭头函数没有自己的 this,它继承外部作用域的 this

  3. 构造函数:
    在构造函数中,this 指向正在创建的新对象。当使用 new 关键字调用构造函数时,this 将绑定到新创建的对象实例。

  4. 事件处理函数:
    在事件处理函数中,this 指向触发事件的元素(DOM 元素),这是一种常见的情况,比如点击事件处理函数中的 this 指向被点击的元素。

  5. 方法调用:
    在对象方法中,this 指向调用方法的对象本身。这种情况下,this 通常用于访问对象的属性和方法。

动态绑定机制:
this 的指向是动态绑定的,它在函数被调用时确定,而不是在函数定义时确定。这意味着 this 的指向可以根据函数的调用方式和上下文发生变化。

在 JavaScript 中,函数的调用方式包括四种:函数调用、方法调用、构造函数调用和通过 callapplybind 方法进行调用。每种调用方式都会影响 this 的指向。

理解 this 的指向和动态绑定机制对于编写正确且易于维护的 JavaScript 代码非常重要。掌握不同情境下 this 的行为可以帮助避免错误并更好地控制代码的执行逻辑。

1. 在全局作用域中:

1
console.log(this); // 在浏览器中输出:Window 对象,在Node.js中输出:global 对象

2. 作为函数调用:

1
2
3
4
5
function myFunction() {
console.log(this);
}

myFunction(); // 在浏览器中输出:Window 对象,在Node.js中输出:global 对象(非严格模式下)

3. 作为方法调用:

1
2
3
4
5
6
7
8
var person = {
name: "Alice",
greet: function() {
console.log(`Hello, my name is ${this.name}`);
}
};

person.greet(); // 输出:Hello, my name is Alice

4. 使用callapply方法显式指定this

1
2
3
4
5
6
7
8
9
function greet() {
console.log(`Hello, my name is ${this.name}`);
}

var person1 = { name: "Alice" };
var person2 = { name: "Bob" };

greet.call(person1); // 输出:Hello, my name is Alice
greet.apply(person2); // 输出:Hello, my name is Bob

callapplybind是 JavaScript 中用于显式绑定函数中的this值的方法。它们在不同情况下提供了对函数的不同方式调用,以及指定函数内部this的能力。

1. call方法:
call方法用于调用函数,第一个参数是函数内部的this值,后面的参数是函数的参数列表。

1
2
3
4
5
6
function greet(message) {
console.log(`${message}, ${this.name}`);
}

var person = { name: "Alice" };
greet.call(person, "Hello"); // 输出:Hello, Alice

2. apply方法:
apply方法与call类似,但是参数需要放在一个数组中传递。

1
2
3
4
5
6
function greet(message) {
console.log(`${message}, ${this.name}`);
}

var person = { name: "Bob" };
greet.apply(person, ["Hi"]); // 输出:Hi, Bob

3. bind方法:
bind方法用于创建一个新的函数,并将其中的this绑定到指定的值,可以在稍后调用该函数。

1
2
3
4
5
6
7
function greet(message) {
console.log(`${message}, ${this.name}`);
}

var person = { name: "Charlie" };
var greetPerson = greet.bind(person);
greetPerson("Hey"); // 输出:Hey, Charlie

这些方法允许开发者显式地控制函数中this的值,以及提供参数调用函数。选择适当的方法取决于具体的使用情况和需求。

5. 作为构造函数调用:

1
2
3
4
5
6
function Person(name) {
this.name = name;
console.log(this); // 输出:Person { name: 'Alice' }
}

var person = new Person("Alice");

6. 箭头函数中的this

1
2
3
4
5
6
7
8
9
10
11
var obj = {
func: function() {
console.log(this); // 输出:{ func: [Function: func] }
var arrowFunc = () => {
console.log(this); // 输出:{ func: [Function: func] }
};
arrowFunc();
}
};

obj.func();

this的原理:
this的值由函数的调用方式决定,而不是函数的定义方式。以下是一些原则:

  1. 默认绑定: 在全局作用域中,this指向全局对象(浏览器中是Window,Node.js中是global)。在普通函数中,this同样指向全局对象(非严格模式下)。

  2. 隐式绑定: 当函数作为对象的方法调用时,this指向调用该函数的对象。

  3. 显式绑定: 使用callapplybind方法,可以显式指定函数中的this

  4. 构造函数中的绑定: 当使用new关键字调用构造函数创建对象时,this会绑定到新创建的对象。

  5. 箭头函数中的绑定: 箭头函数中的this由外围(定义箭头函数的函数或作用域)的this决定,而不是运行时调用的对象。

理解这些原则有助于更好地理解this的行为,避免出现意外的结果。

异步编程

解释回调函数、Promise、async/await、Generator等异步编程模式。了解事件循环(Event Loop)和宏任务与微任务的区别。

异步编程是指在程序执行过程中,某些操作不会阻塞程序的执行,而是在后台执行,以提高程序的效率和响应性。以下是几种常见的异步编程模式:

  1. 回调函数: 回调函数是一种通过将函数作为参数传递给另一个函数,在异步操作完成后执行的方式。经典的回调地狱问题是多个嵌套的回调函数难以管理和维护。

  2. Promise: Promise 是一种用于处理异步操作的对象,它表示一个可能完成或失败的操作,并提供了更加结构化的异步编程方式。通过 then 方法可以在操作完成后执行成功或失败的回调。

  3. async/await: async 函数是异步操作的新的语法糖,它可以让你像编写同步代码一样处理异步操作。await 关键字用于等待一个 Promise 完成,并返回结果。

  4. Generator: Generator 是一种特殊类型的函数,可以在运行中暂停并在需要时恢复执行。通过 yield 关键字可以暂停函数的执行,而通过迭代器可以在每次调用 next 时恢复执行。

事件循环(Event Loop):
事件循环是 JavaScript 异步编程的基础机制,它允许程序在单线程中处理多个任务。JavaScript 引擎中存在一个事件循环,它会不断地从任务队列中获取任务并执行。

宏任务与微任务的区别:
事件循环中的任务分为两种类型:宏任务(macrotask)和微任务(microtask)。

  • 宏任务(macrotask): 宏任务包括整体代码块、setTimeout、setInterval、I/O 操作等。宏任务会在事件循环的不同阶段执行,它们会被排队并在主线程空闲时执行。

  • 微任务(microtask): 微任务包括 Promise 的回调、MutationObserver 和 process.nextTick 等。微任务会在宏任务执行完毕后立即执行,在事件循环的当前阶段中执行,不会被推迟。

事件循环的过程是这样的:首先执行当前宏任务,然后检查是否有微任务需要执行,如果有,执行微任务直到清空微任务队列,然后继续执行下一个宏任务,以此类推。

总结:异步编程模式有多种,它们用于处理非阻塞的操作,以提高程序的性能和响应性。事件循环是 JavaScript 异步编程的核心机制,它通过宏任务和微任务的处理,实现了单线程环境下的多任务处理。

Event Bubbling和Event Capturing

解释事件冒泡和事件捕获的机制。了解如何阻止事件传播和取消默认行为。

事件冒泡(Event Bubbling)和事件捕获(Event Capturing)是指在DOM结构中,当一个元素上的事件被触发时,事件会沿着其父级元素一直传播或捕获的过程。

事件冒泡机制:
事件冒泡是指当一个元素上的事件被触发时,事件会从内向外依次传播到父元素,直至达到文档的根元素。比如,当点击一个按钮时,按钮的点击事件会首先触发按钮自身的事件处理函数,然后逐级向上触发父级元素的事件处理函数。

事件捕获机制:
事件捕获是指当一个元素上的事件被触发时,事件会从文档根元素开始,沿着DOM树从外向内传播,直到达到事件触发元素。比如,当点击一个按钮时,事件会首先从文档根元素开始捕获,逐级向下,直到达到按钮元素,然后触发按钮的事件处理函数。

阻止事件传播和取消默认行为:

  1. 阻止事件传播(Stop Propagation): 可以使用 event.stopPropagation() 方法阻止事件继续传播,无论是冒泡阶段还是捕获阶段。这会阻止其他祖先或子孙元素上的事件处理函数被触发。

  2. 取消默认行为(Prevent Default): 可以使用 event.preventDefault() 方法取消浏览器默认的事件行为。例如,点击链接时阻止页面跳转、在表单提交时阻止页面刷新等。

示例代码如下:

1
2
3
4
5
6
7
8
9
10
document.getElementById("parent").addEventListener("click", function(event) {
console.log("Parent clicked");
// event.stopPropagation(); // 阻止事件冒泡
}, true); // 第三个参数表示在捕获阶段触发事件

document.getElementById("child").addEventListener("click", function(event) {
console.log("Child clicked");
// event.stopPropagation(); // 阻止事件冒泡
// event.preventDefault(); // 取消默认行为
});

通过理解事件冒泡和捕获机制,以及如何阻止事件传播和取消默认行为,你可以更好地控制和处理DOM元素上的事件,实现更复杂的交互效果。

DOM操作

解释如何通过JavaScript操作DOM元素,包括查找元素、修改属性、添加/删除元素等。注意性能问题和重绘回流。

通过JavaScript操作DOM元素是实现动态网页交互的重要部分。以下是一些常见的DOM操作以及与性能相关的注意事项:

查找元素:

  1. getElementById: 使用元素的ID获取一个单一元素。
  2. querySelector: 使用CSS选择器获取匹配的第一个元素。
  3. querySelectorAll: 使用CSS选择器获取匹配的所有元素。
  4. getElementsByClassName: 使用类名获取匹配的所有元素。
  5. getElementsByTagName: 使用标签名获取匹配的所有元素。

修改属性:

  1. getAttribute / setAttribute: 获取或设置元素的属性。
  2. classList: 使用classList对象修改元素的类名,可以添加、删除、切换类名。

添加/删除元素:

  1. createElement: 创建新的元素节点。
  2. appendChild / insertBefore: 添加新元素到现有元素中。
  3. removeChild: 从父元素中移除子元素。

性能问题和重绘回流:

  1. 重绘(Repainting): 当元素的样式属性(如颜色、背景)发生变化时,浏览器需要重新绘制元素,这称为重绘。重绘不一定导致布局的改变。

  2. 回流(Reflow): 当元素的尺寸、位置或布局发生变化时,浏览器需要重新计算布局和绘制,这称为回流。回流会导致页面重新排列,影响性能。

性能优化建议:

  1. 批量处理: 尽量减少DOM操作,最好通过文档片段(DocumentFragment)一次性添加多个元素,减少回流次数。

  2. 避免频繁访问样式和布局属性: 避免在循环中频繁读取布局属性,这可能触发多次回流。

  3. 缓存DOM查询: 将查找的DOM元素存储在变量中,避免重复查找。

  4. 使用classList代替直接修改className: 使用classList可以更高效地操作类名。

  5. 避免强制同步布局: 获取某些属性(如offsetTop、offsetLeft、offsetWidth、offsetHeight)会触发回流,尽量避免在频繁变动中使用。

  6. 使用requestAnimationFrame: 使用requestAnimationFrame可以优化动画等频繁的DOM操作,使它们在浏览器的下一帧绘制前执行。

理解DOM操作和性能问题是开发高性能网页的关键。通过合理优化DOM操作,可以减少页面的回流和重绘,提升用户体验。

浏览器渲染

浏览器的渲染过程包括多个阶段,涵盖从接收HTML文档到展示可见内容的一系列步骤。以下是浏览器的详细渲染步骤:

1. 解析HTML: 浏览器解析HTML文档,构建DOM(文档对象模型)树,表示页面的结构和元素关系。

2. 构建DOM树: 浏览器根据HTML标记创建DOM树,每个标签都对应DOM树中的一个节点。

3. 构建CSSOM(CSS对象模型)树: 浏览器解析CSS样式,创建CSSOM树,表示样式规则的层叠关系。

4. 合并DOM和CSSOM: 浏览器将DOM树和CSSOM树合并成一个Render树,Render树描述了页面的可见内容、布局和样式。

5. 布局(Layout): 浏览器计算出每个元素在屏幕上的位置和大小,生成布局(盒子模型)信息。

6. 绘制(Paint): 浏览器将页面的可见部分绘制在内存中,生成位图。

7. 栅格化(Rasterization): 将绘制好的位图转换为图像块,以便渲染到屏幕上。

8. 合成(Composite): 浏览器将多个图像块合成为最终的屏幕图像,按照正确的层次和顺序进行合成。

9. 渲染到屏幕: 最终的屏幕图像渲染到显示器上,用户可以看到页面内容。

值得注意的是,上述步骤在实际渲染过程中可能会有优化和并行处理。例如,浏览器可能会对DOM树和CSSOM树的解析进行异步操作,以提高页面加载速度。

整个渲染过程是一个复杂的流程,各个阶段的优化都会影响到页面的渲染性能和用户体验。理解浏览器的渲染步骤有助于开发者优化网页性能,提高用户满意度。

内存管理和垃圾回收

了解JavaScript中的垃圾回收机制,如何管理内存、避免内存泄漏,以及手动解除引用的技巧。

JavaScript中的垃圾回收机制是自动管理内存的过程,它会检测不再使用的变量和对象,并释放其占用的内存。以下是关于垃圾回收、内存管理、避免内存泄漏和手动解除引用的详细解释:

垃圾回收机制:
JavaScript的垃圾回收机制主要依赖于两个主要的策略:标记清除和引用计数。

  • 标记清除(Mark-and-Sweep): 垃圾回收器会定期标记所有在内存中的变量,然后标记所有仍然可访问的变量。未被标记的变量被视为垃圾,可以被清除。

  • 引用计数: 引用计数是一种简单的垃圾回收方法,它跟踪每个对象被引用的次数。当一个对象的引用计数为零时,即没有变量引用它,那么它就是不可访问的,可以被回收。

内存管理和避免内存泄漏:

  • 正确使用变量作用域: 确保变量只在需要的范围内存在,避免变量持续存在于不需要的作用域中。

  • 显式解除引用: 当不再需要一个对象时,将其引用设置为null,这样垃圾回收器可以正确地判定它不再被引用。

  • 定时清除计时器: 使用setTimeoutsetInterval创建的计时器可能导致内存泄漏。确保在不需要计时器时及时清除它们。

  • 避免循环引用: 循环引用是两个或多个对象相互引用,而没有任何外部引用。确保在不需要的情况下断开循环引用。

手动解除引用的技巧:
手动解除引用是指在适当的时候将对象的引用设置为null,以确保垃圾回收器可以回收不再使用的对象。以下是一些手动解除引用的技巧:

  1. 退出函数时解除引用: 在函数内部创建的局部变量,在函数退出时会自动解除引用。确保不会在函数外部继续引用这些局部变量。

  2. 清除事件监听器: 当不再需要一个元素的事件监听器时,确保将其移除,以避免元素和监听器之间的循环引用。

  3. 清除计时器: 使用clearTimeoutclearInterval清除不再需要的计时器,以避免内存泄漏。

  4. 清除闭包: 如果一个函数形成了闭包,确保在不再需要时解除对其变量的引用,以避免造成循环引用。

通过合理的内存管理和手动解除引用,可以减少内存泄漏问题,确保程序在长时间运行中仍然具有良好的性能。

模块化

介绍模块化的概念、CommonJS、ES6模块和导入/导出的用法。了解模块加载器和打包工具(如Webpack)。

模块化的概念:
模块化是一种将程序分割成独立、可复用的代码块的编程方法。它有助于将代码分解为更小的部分,提高可维护性、可读性和复用性。模块化还有助于隔离命名空间,避免全局变量污染,减少命名冲突。

CommonJS:
CommonJS是一种用于在服务器端和本地环境中实现模块化的规范。它定义了requiremodule.exports来导入和导出模块。在Node.js中,CommonJS是默认的模块系统。

ES6模块:
ES6(ECMAScript 2015)引入了一种原生的模块系统,支持在浏览器环境中使用模块化。ES6模块使用importexport语法来导入和导出模块。它可以在现代浏览器中直接使用,也可以通过转译工具(如Babel)在旧浏览器中使用。

导入/导出的用法:
在CommonJS和ES6模块中,导入和导出模块的方式略有不同:

CommonJS:

1
2
3
4
5
6
7
// 导出模块
module.exports = {
key: value
};

// 导入模块
const module = require('./modulePath');

ES6模块:

1
2
3
4
5
// 导出模块
export const key = value;

// 导入模块
import { key } from './modulePath';

模块加载器和打包工具(如Webpack):
模块加载器是用于在浏览器环境中加载模块的工具,它可以将模块的依赖关系和导入关系解析成可执行的代码。常见的模块加载器包括RequireJS和SystemJS。

打包工具(如Webpack)是用于将多个模块打包成一个或多个文件的工具。它可以自动解析模块之间的依赖关系,并生成最终的代码包。Webpack还提供了许多功能,如代码分割、压缩、文件处理等,以优化前端项目的性能和开发流程。

综上所述,模块化是现代编程中的重要概念,通过将代码分割成模块,可以提高代码的可维护性和可读性。不同的模块化规范和工具可以帮助开发者更好地组织和管理项目代码。

闭包、高阶函数和函数式编程

解释高阶函数、柯里化、偏函数等概念。掌握函数式编程的基本思想和应用。

高阶函数:
高阶函数是指接受一个或多个函数作为参数,并/或返回一个函数作为结果的函数。它们可以用于将函数抽象出来,使代码更具可复用性和灵活性。常见的高阶函数包括mapfilterreduce等。下面是一个简单的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 高阶函数:接受函数作为参数
function applyOperation(value, operation) {
return operation(value);
}

function square(x) {
return x * x;
}

function double(x) {
return x * 2;
}

console.log(applyOperation(5, square)); // 输出:25
console.log(applyOperation(3, double)); // 输出:6

柯里化(Currying):
柯里化是将接受多个参数的函数转换为一系列只接受一个参数的函数的过程。柯里化可以使函数的组合更加灵活,可以逐步传递参数,更好地实现函数的复用。下面是一个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 常规函数
function add(x, y) {
return x + y;
}

// 柯里化函数
function curriedAdd(x) {
return function(y) {
return x + y;
};
}

const add5 = curriedAdd(5);
console.log(add5(3)); // 输出:8

偏函数:
偏函数是指通过固定部分参数来创建一个新的函数。它可以将一个多参数函数转化为一个接受较少参数的函数。这在部分参数固定的情况下非常有用,减少了重复代码。下面是一个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 常规函数
function multiply(x, y) {
return x * y;
}

// 偏函数
function partial(fn, x) {
return function(y) {
return fn(x, y);
};
}

const double = partial(multiply, 2);
console.log(double(5)); // 输出:10

类型转换和比较

解释强制类型转换、隐式类型转换、全等运算符和相等运算符的区别。理解JavaScript的数据类型。

强制类型转换:
强制类型转换是将一个数据类型转换为另一个数据类型的过程,通常由开发者显式地执行。在JavaScript中,可以使用函数(如Number()String()Boolean())来进行强制类型转换。

隐式类型转换:
隐式类型转换是在操作中自动发生的类型转换,通常由JavaScript引擎根据上下文和操作符自动执行。例如,当使用不同类型的值进行加法运算时,JavaScript会隐式地将它们转换为相同的类型再执行操作。

全等运算符(===)和相等运算符(==)的区别:

  • 全等运算符(===): 它比较两个值是否相等,不仅比较值本身,还比较数据类型。只有在值和数据类型都相等时,全等运算符返回true

  • 相等运算符(==): 它也比较两个值是否相等,但在比较之前会进行隐式类型转换。它会尝试将操作数转换为相同的类型,然后再比较值。因此,可能会导致一些意外的结果,例如不同类型的值被转换为相同的类型后看起来相等。

JavaScript的数据类型:
JavaScript具有多种数据类型,可以分为两大类:基本数据类型(Primitive Types)和引用数据类型(Reference Types)。

判断null的类型

在JavaScript中,可以使用typeof操作符来判断变量的类型。然而,typeof操作符对于null的返回结果可能会让人感到困惑,因为它返回的是"object",而不是"null"。这是一个历史遗留问题。

1
2
var value = null;
console.log(typeof value); // 输出:"object"

如果你需要更准确地判断变量是否为null,可以使用以下方法:

1. 使用严格相等(===):

1
2
3
4
var value = null;
if (value === null) {
console.log("Value is null");
}

2. 使用isNull函数(自定义或使用第三方库):

1
2
3
4
5
6
7
8
function isNull(value) {
return value === null;
}

var value = null;
if (isNull(value)) {
console.log("Value is null");
}

3. 使用nullish判断(??): 如果你想检查一个值是否为nullundefined,可以使用??操作符。

1
2
3
4
5
6
var value = null;
if (value ?? "default value") {
console.log("Value is not null or undefined");
} else {
console.log("Value is null or undefined");
}

总之,尽管typeof操作符对于null的结果可能会有点令人困惑,但你可以使用严格相等、自定义函数或??操作符来更准确地判断一个值是否为null

基本数据类型:

  1. Number: 表示数字,包括整数和浮点数。
  2. String: 表示字符串,由一系列字符组成。
  3. Boolean: 表示布尔值,只有两个值:truefalse
  4. Null: 表示空值。
  5. Undefined: 表示未定义的值。
  6. Symbol(ES6新增): 表示唯一的、不可变的值。

引用数据类型:

  1. Object: 表示对象,是一种键值对的集合。
  2. Array: 表示数组,可以存储多个值。
  3. Function: 表示函数。
  4. Date: 表示日期和时间。
  5. RegExp: 表示正则表达式。

跨域问题

解释浏览器的同源策略、JSONP、CORS、代理等跨域解决方案。

同源策略:
同源策略是浏览器的一种安全策略,它限制了一个页面从一个源加载的文档或脚本如何与来自其他源的资源进行交互。同源是指协议、域名和端口都相同。

JSONP(JSON with Padding):
JSONP是一种解决跨域访问的方法,它利用<script>标签的跨域能力来实现。通过动态创建<script>标签,将要获取的数据包装在一个回调函数中,让服务器返回调用该回调函数的脚本,从而实现跨域请求。

CORS(跨源资源共享):
CORS是一种官方支持的跨域解决方案。它通过在HTTP头部添加一些特定的头字段来允许服务器声明哪些来源被允许访问资源。前端发起跨域请求时,浏览器会自动发送CORS预检请求,如果服务器返回合适的CORS头部,浏览器允许跨域访问。

代理:
代理是一种通过在同源服务器上创建一个中间层来转发请求和响应的方法。前端通过访问同源服务器,然后由服务器代理请求真正的目标服务器,再将响应返回给前端,实现跨域访问。

这些跨域解决方案各有优缺点:

  • JSONP: 适用于GET请求,安全性较差,只能实现一些简单的跨域需求。
  • CORS: 支持各种HTTP请求,更加安全可靠,但需要服务器端配合设置CORS头部。
  • 代理: 适用于所有请求,但需要同源服务器支持和维护。

选择合适的跨域解决方案取决于项目的需求和实际情况。

面向对象编程

解释JavaScript中的面向对象特性,如对象、类、构造函数、继承等。

JavaScript是一门基于原型的面向对象编程语言,它的面向对象特性包括以下几个重要概念:

1. 对象: 对象是JavaScript中的基本数据结构,可以存储属性和方法。属性可以是基本类型或其他对象,方法是一组可以被调用的函数。对象是按照键值对的方式组织属性和方法的。

2. 构造函数: 构造函数是用于创建对象的特殊函数,通过new关键字调用。构造函数可以被看作是创建对象的蓝图,它定义了对象的属性和方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
function Person(name, age) {
this.name = name;
this.age = age;
this.sayHello = function() {
console.log(`Hello, my name is ${this.name}`);
};
}

const person1 = new Person("Alice", 25);
const person2 = new Person("Bob", 30);

person1.sayHello(); // 输出:Hello, my name is Alice
person2.sayHello(); // 输出:Hello, my name is Bob

3. 原型(Prototype): 每个JavaScript对象都有一个原型对象,它允许对象继承属性和方法。构造函数的prototype属性指向原型对象,而对象通过__proto__属性访问原型。

1
2
3
4
5
6
7
8
9
10
function Dog(name) {
this.name = name;
}

Dog.prototype.bark = function() {
console.log(`${this.name} is barking`);
};

const dog1 = new Dog("Buddy");
dog1.bark(); // 输出:Buddy is barking

4. 继承: 通过原型链,JavaScript实现了原型继承。一个对象可以从另一个对象继承属性和方法。子类对象的原型指向父类对象,从而实现继承。

1
2
3
4
5
6
7
8
9
10
function Student(name, grade) {
this.name = name;
this.grade = grade;
}

Student.prototype = Object.create(Person.prototype);
Student.prototype.constructor = Student;

const student1 = new Student("Eva", 5);
student1.sayHello(); // 输出:Hello, my name is Eva

JavaScript的面向对象特性基于原型机制,它的灵活性和动态性使得对象可以在运行时进行扩展和修改。虽然与传统类和继承的面向对象语言有些不同,但JavaScript的面向对象编程是其重要的编程范式之一。

定时器和事件循环

解释setTimeoutsetInterval以及requestAnimationFrame的使用,理解事件循环的机制。

setTimeoutsetInterval的使用:

这两个函数都是用于在一段时间后执行代码或重复执行代码。

  1. setTimeout

    • setTimeout函数用于在一定时间后执行一次指定的函数。
    • 语法:setTimeout(function, delay)
    • 第一个参数是要执行的函数,第二个参数是延迟的毫秒数。
    • 示例:
      1
      2
      3
      setTimeout(function() {
      console.log("Delayed log after 1000ms");
      }, 1000);
  2. setInterval

    • setInterval函数用于在一定时间间隔内重复执行指定的函数。
    • 语法:setInterval(function, delay)
    • 第一个参数是要执行的函数,第二个参数是时间间隔的毫秒数。
    • 示例:
      1
      2
      3
      setInterval(function() {
      console.log("Repeated log every 2000ms");
      }, 2000);

requestAnimationFrame的使用:
requestAnimationFrame是用于在浏览器下一次重绘之前执行指定的函数,通常用于制作动画。

  • requestAnimationFrame会根据浏览器的绘制周期自动调用函数,通常与递归结合,创建平滑的动画效果。
  • 语法:requestAnimationFrame(callback)
  • 示例:
    1
    2
    3
    4
    5
    6
    7
    function animate(timestamp) {
    // 执行动画逻辑
    // 在下一次重绘前调用自身
    requestAnimationFrame(animate);
    }

    requestAnimationFrame(animate);

事件循环的机制:
事件循环是JavaScript异步执行的基础。在浏览器环境中,JavaScript运行在单线程中,通过事件循环机制来处理异步任务,以避免阻塞主线程。

事件循环的基本流程:

  1. 从消息队列中获取任务,将其添加到调用栈(执行栈)中执行。
  2. 如果调用栈为空,从消息队列中取下一个任务,执行之。
  3. 重复步骤1和2,直至消息队列为空。

事件循环机制保证了异步任务(如setTimeout、网络请求、事件处理等)能够按照顺序执行,避免了阻塞主线程。setTimeoutsetInterval将任务放入消息队列,而requestAnimationFrame则在浏览器下一次绘制前调用函数,确保动画平滑执行。

性能优化

了解如何优化JavaScript性能,包括减少重绘回流、懒加载、代码分割等。

优化JavaScript性能是确保网页快速加载和响应的关键。以下是一些优化技巧,包括减少重绘回流、懒加载、代码分割等:

1. 减少重绘和回流:

  • 使用CSS Transitions 和 CSS Animations 来代替使用 JavaScript 实现的动画效果,因为 CSS 动画在绘制上更高效。
  • 避免频繁地修改DOM样式和布局属性,可以使用类名一次性修改多个属性。
  • 使用transform属性进行位移、旋转和缩放,因为它不会引起回流。

2. 懒加载(Lazy Loading):

  • 对于图片、视频等资源,使用懒加载技术来延迟加载这些内容,直到用户滚动到它们附近。
  • 使用<img>loading="lazy"属性来实现图片的懒加载。

3. 代码分割(Code Splitting):

  • 将应用拆分成多个模块或chunk,只加载当前页面所需的代码,从而加速初始加载时间。
  • 使用工具(如Webpack)来自动进行代码分割,确保按需加载模块。

4. 压缩和混淆:

  • 压缩JavaScript、CSS和HTML文件,减少文件大小,加快加载速度。
  • 使用工具(如Terser)来进行JavaScript的代码混淆,减少不必要的代码和变量名。

5. 避免不必要的HTTP请求:

  • 将多个小的文件合并成一个大文件,减少HTTP请求次数。
  • 使用CSS Sprites将多个小图标合并成一个图片,减少加载时间。

6. 缓存机制:

  • 使用浏览器缓存,将资源缓存在用户本地,提高再次访问速度。
  • 设置适当的缓存头部,例如使用Cache-ControlExpires

7. 使用Web Workers:

  • 使用Web Workers在后台线程中处理一些耗时的任务,避免阻塞主线程,提升页面的响应性能。

8. 优化图片和媒体资源:

  • 使用适当的图片格式,如JPEG、PNG、WebP,以及压缩工具来优化图片大小。
  • 对图片进行懒加载,仅在它们进入视口时加载。

9. 避免同步请求:

  • 避免在主线程中执行耗时的同步请求,使用异步请求(如XMLHttpRequest、Fetch API)。

综合使用这些优化技巧,可以显著提升网页的性能,使用户体验更加流畅。然而,需要根据具体的项目情况进行评估和优化。

防抖

例如实时监控用户输入的时候,为防止用户输入每个字符都要进行监控操作(发请求、校验等等),可以设置定时器使用户在输入完成之后再进行下一步操作,达到防抖效果,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
const input = document.getElementById('input')

function debounce(fn, delay) { // 定义一个debounce函数,传入操作函数和延迟时间
let timer = nul
return function () {
if (timer) clearTimeout(timer) // 如果有定时器就重置
timer = setTimeout(() => {
fn.apply(this, arguments) // apply 改变 this 指向,使调用者的this能够指向input
}, delay)
}

input.addEventListener( // 设置一个input的事件监听
'input',
debounce(function (event) { // 不使用箭头函数也是为了能够正确拿到this和event对象
console.log(this)
console.log(event)
console.log('发送请求')
}, 500)
)

节流

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
const box = document.querySelector('.box')

// 节流:一段时间内,只执行一次某个操作;过了这段时间,还有操作的话,继续执行新的
function throttle(fn, delay) {
let timer = nu
return function () {
if (timer) return // 如果timer还有的话,就不触发事件
timer = setTimeout(() => {
fn.apply(this, arguments)
timer = null // timer置空使delay过了之后又可以执行fn()
}, delay)
}

box.addEventListener(
'drag',
throttle(function (event) {
// console.log(event.offsetX, event.offsetY)
console.log('test')
}, 300)
)

Promise

Promise是JavaScript中处理异步操作的一种机制,用于更优雅地管理异步代码和处理回调地狱(callback hell)问题。它提供了一种更结构化和可读性更高的方式来处理异步操作,以及在操作完成后获取结果或处理错误。

Promise的基本概念如下:

  • Pending(进行中): Promise对象的初始状态,表示异步操作尚未完成。

  • Fulfilled(已完成): 表示异步操作成功完成,此时会调用then方法中的回调函数,获取操作结果。

  • Rejected(已拒绝): 表示异步操作失败,此时会调用catch方法中的回调函数,处理错误。

Promise的基本用法示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const myPromise = new Promise((resolve, reject) => {
// 异步操作
if (/* 操作成功 */) {
resolve("操作成功的结果");
} else {
reject("操作失败的原因");
}
});

myPromise
.then(result => {
console.log("成功:" + result);
})
.catch(error => {
console.error("失败:" + error);
});

Promise的原理可以简要概括如下:

  1. Promise的构造: 创建一个Promise对象时,会传入一个执行器函数,这个函数接受两个参数:resolvereject。在异步操作完成时,调用resolve表示操作成功,调用reject表示操作失败。

  2. 状态的改变: 在执行器函数中,调用resolvereject会改变Promise的状态,从Pending变为Fulfilled或Rejected。

  3. then和catch方法: Promise提供了thencatch方法来处理操作成功和操作失败的情况。then方法接受一个成功回调函数,catch方法接受一个失败回调函数。

  4. 链式调用: 通过返回新的Promise,可以实现链式调用。这使得可以在不同的步骤中串联处理多个异步操作。

  5. 解决回调地狱: 使用Promise可以更清晰地组织异步代码,避免多层嵌套的回调函数,提高了代码的可读性和维护性。

需要注意的是,虽然Promise可以解决回调地狱问题,但在复杂的异步场景下,仍然可能导致代码难以理解。为了进一步简化异步代码,ES6引入了async/await语法,可以更直观地编写异步代码,基于Promise实现。

闭包

闭包是JavaScript中的一个重要概念,它涉及作用域和函数的关系,通常用来描述函数内部可以访问外部作用域的情况。一个闭包是由函数以及声明在该函数内部的所有变量组成的,这些变量可以继续在函数执行完毕后被访问。

简单地说,闭包就是一个函数,它可以访问它被创建时所处的词法作用域中的变量,即使这个函数在不同的作用域中被调用。闭包在处理一些高级的编程模式和异步编程时非常有用。

以下是闭包的一些关键特点:

  1. 函数嵌套: 闭包通常是由一个函数内部定义另一个函数所创建的。内部函数可以访问外部函数中的变量和参数,即使外部函数已经执行完毕。

  2. 变量的引用: 内部函数持有外部函数作用域的引用,使得外部函数中的变量不会被垃圾回收,直到内部函数不再被引用为止。

  3. 保护状态: 闭包可以被用于保护变量状态,防止外部访问或修改,实现了数据的私有性。

  4. 实现数据封装: 闭包可以将变量封装在函数内部,只暴露特定的接口来访问和操作变量,实现了信息隐藏。

  5. 异步编程: 在异步编程中,闭包可以解决回调函数无法访问正确作用域的问题。例如,在事件处理函数中使用闭包来访问外部函数的变量。

以下是一个简单的闭包示例:

1
2
3
4
5
6
7
8
9
10
11
12
function outerFunction() {
var outerVar = "I'm outside!";

function innerFunction() {
console.log(outerVar); // 内部函数可以访问外部函数的变量
}

return innerFunction;
}

var closure = outerFunction(); // 返回内部函数,形成闭包
closure(); // 调用内部函数,输出 "I'm outside!"

需要注意的是,虽然闭包非常有用,但过多地使用闭包可能导致内存占用和性能问题。因为闭包会持有外部作用域的引用,当闭包在不再需要的时候,需要手动解除引用,以便垃圾回收可以释放内存。在使用闭包时,需要权衡性能和内存消耗。

作用域和作用域链

作用域(Scope)是指变量和函数在代码中的可访问范围。在JavaScript中,作用域决定了哪些变量和函数可以在哪里被访问,以及它们是否可见。JavaScript采用词法作用域(也称为静态作用域),意味着作用域在代码编写时就确定了,而不是在运行时确定。

作用域链(Scope Chain)是指由嵌套的函数作用域所构成的一条链,它决定了一个变量在哪个作用域中被查找和访问。作用域链的顶端是当前函数的作用域,然后逐级往上查找,直到全局作用域为止。

以下是一些关键概念和解释:

  1. 全局作用域: 在最外层定义的变量和函数具有全局作用域,可以在任何地方访问。

  2. 函数作用域: 函数内部定义的变量和函数具有函数作用域,只能在该函数内部访问。

  3. 块级作用域: 由ES6引入,用于控制if语句、循环等块中定义的变量的作用范围。

  4. 作用域链的建立: 当访问一个变量时,JavaScript引擎首先在当前作用域中查找,如果找不到则继续向上一级作用域查找,直到全局作用域。

  5. 闭包中的作用域链: 在一个函数内部定义的函数可以访问外部函数的作用域,形成了闭包。这就是闭包的作用域链。

  6. 变量提升: 在一个作用域中,变量声明会被提升到作用域的顶部,但赋值不会。这意味着你可以在声明前使用变量,但它的值将是undefined

以下是一个简单的作用域和作用域链示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var globalVar = "Global";

function outer() {
var outerVar = "Outer";

function inner() {
var innerVar = "Inner";
console.log(innerVar); // 在内部函数中访问内部变量
console.log(outerVar); // 在内部函数中访问外部函数作用域的变量
console.log(globalVar); // 在内部函数中访问全局变量
}

inner();
}

outer();

理解作用域和作用域链是理解JavaScript中变量访问和作用域限制的关键。它对于理解闭包、函数嵌套以及变量的可见性和生命周期等概念非常重要。

原型和原型链

原型(Prototype):
在JavaScript中,每个对象都有一个原型,它是一个对象,用于存储共享的属性和方法。当你访问一个对象的属性或方法时,如果对象本身没有这个属性或方法,JavaScript会沿着原型链查找,找到原型对象上的对应属性或方法。原型是一个对象与对象之间的关系,它通过引用连接在一起。

原型链(Prototype Chain):
原型链是由一系列的原型对象组成的链式结构,用于属性和方法的查找。当访问一个对象的属性或方法时,JavaScript引擎会首先在对象自身查找,如果找不到,则沿着原型链向上一级的原型对象查找,直到找到属性或方法或者到达原型链的顶端(Object.prototype)。

以下是原型和原型链的一些关键点:

  1. 原型对象的创建: 在JavaScript中,每个函数都有一个prototype属性,指向一个原型对象。这个原型对象可以包含共享的属性和方法。

  2. 实例对象和原型的关系: 当通过一个构造函数创建对象实例时,实例会继承构造函数的原型对象上的属性和方法。这使得对象实例可以共享相同的属性和方法。

  3. 原型链的构建: 当访问一个对象的属性或方法时,JavaScript引擎会先在对象自身查找,如果找不到,它会向上一级的原型对象查找,依此类推,直到找到或到达原型链的顶端。

  4. Object.prototype: 原型链的顶端是Object.prototype,它是所有对象的原型。它包含JavaScript内置的一些方法,如toStringhasOwnProperty等。

  5. 属性查找顺序: 原型链的属性查找是从下到上的,即先在对象自身查找,然后再在原型对象上查找,以此类推。

以下是一个简单的原型和原型链示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 构造函数
function Person(name) {
this.name = name;
}

// 添加原型方法
Person.prototype.sayHello = function() {
console.log("Hello, I'm " + this.name);
};

// 创建实例
var person1 = new Person("Alice");

// 调用实例方法
person1.sayHello(); // 输出 "Hello, I'm Alice"

// 查找属性和方法
console.log(person1.name); // 从实例中查找 "name"
console.log(person1.sayHello); // 从原型对象中查找 "sayHello"

理解原型和原型链是理解JavaScript中对象继承的基础。它对于理解构造函数、继承模式以及面向对象编程在JavaScript中的实现方式非常重要。

ES5

在面试中,ES5(ECMAScript 5)和ES6(ECMAScript 2015,也称为ES2015或ES6)都是常见的考点,因为它们是JavaScript语言中的两个重要版本,带来了许多新的语法、特性和改进。以下是在面试中可能涉及到的一些ES5和ES6的考点:

ES5 考点:

  1. 变量声明: 包括使用varletconst进行变量声明的区别、作用域等。

  2. 函数: 包括函数声明和函数表达式的区别、函数作用域、闭包、arguments对象等。

  3. 作用域和闭包: 解释作用域链、闭包的概念和原理,以及如何避免闭包引发的内存泄漏。

  4. 原型和原型链: 解释原型、原型链的概念,以及如何实现继承。

  5. 同步和异步: 解释回调函数、事件循环、异步编程等。

  6. 事件处理: 解释事件冒泡和事件捕获机制,以及如何添加和移除事件监听器。

ES6 考点:

  1. let 和 const: 解释letconst关键字,以及它们与var的区别,包括块级作用域等。

  2. 箭头函数: 解释箭头函数的特性,以及它与普通函数的区别。

  3. 模板字符串: 解释模板字符串的语法和用法,包括插值、多行字符串等。

  4. 解构赋值: 解释解构赋值的概念和用法,包括数组和对象的解构。

  5. 展开运算符: 解释展开运算符的用法,包括在数组、对象和函数调用中的应用。

  6. 类和继承: 解释ES6中的类和继承,以及与ES5原型继承的区别。

  7. Promise 和异步编程: 解释Promise的概念、用法,以及如何处理异步编程。

  8. 生成器和迭代器: 解释生成器函数和迭代器的概念,以及它们在异步编程中的应用。

  9. 模块化: 解释ES6模块的语法和用法,包括导入和导出等。

  10. 新的数据结构: 解释Set、Map、WeakSet、WeakMap等新的数据结构和用法。

在面试中,了解这些ES5和ES6的知识点,以及它们的语法、特性和用法,会帮助你更好地回答相关问题,展现你的JavaScript编程能力。

  • 标题: 前端面试笔记
  • 作者: liohi
  • 创建于 : 2023-08-29 21:04:25
  • 更新于 : 2023-08-29 21:10:10
  • 链接: https://liohi.github.io/2023/08/29/面试/
  • 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。
 评论