当前位置: 首页 > news >正文

原生JavaScript核心概念解析:作用域、原型、异步与事件循环

1. 为什么原生JS面试题是前端能力的试金石

最近在帮团队面试前端同学,也和一些资深面试官交流,发现一个挺有意思的现象:很多候选人谈起Vue、React的响应式原理、虚拟DOM、Hooks这些框架层面的东西头头是道,但一碰到几道看似基础的原生JavaScript题目,思路就开始卡壳,甚至暴露出对语言本身理解的偏差。这让我想起自己刚入行时,一位前辈说的话:“框架是招式,JS内功才是根基。招式可以速成,内功却需要日积月累。” 这句话到现在依然适用。

所谓“原生JS面试题”,考察的正是这份“内功”。它不问你如何用Vue的v-for渲染列表,而是问你for...infor...of遍历数组和对象时究竟有何不同;不问你React的useEffect怎么用,而是问你闭包在循环中创建函数时为什么会出问题,以及如何解决。这些问题直指JavaScript语言的核心机制:作用域与闭包、原型与继承、事件循环、类型系统、异步编程等。掌握它们,意味着你能真正理解你写的代码在底层是如何运行的,而不仅仅是记住某个API的调用方式。

这份整理了1-30题的清单,正是围绕这些核心内功展开的。它不是为了刁难人,而是帮你系统地检验和巩固对JavaScript语言本身的理解。无论你是正在准备面试的求职者,还是希望夯实基础、突破瓶颈的在职开发者,静下心来把这些题目搞懂、吃透,都会让你对代码的掌控力上一个台阶。你会发现,之前很多靠“背”下来的框架“黑魔法”,其底层原理其实就藏在这些基础概念里。

2. 作用域、闭包与this指向:代码执行环境的底层逻辑

这是JavaScript中最容易混淆,也最常被考察的三个核心概念。它们共同决定了变量在哪里被定义、函数如何访问外部变量,以及函数内部的this关键字到底指向谁。

2.1 作用域链与变量查找机制

JavaScript的作用域是词法作用域(静态作用域),意味着函数的作用域在函数定义时就决定了,而不是在调用时。当访问一个变量时,引擎会从当前作用域开始查找,如果没找到,就向外层作用域继续查找,直到全局作用域,形成一条“作用域链”。

var globalVar = 'global'; function outer() { var outerVar = 'outer'; function inner() { var innerVar = 'inner'; console.log(innerVar); // 从当前作用域找到 'inner' console.log(outerVar); // 从外层outer作用域找到 'outer' console.log(globalVar); // 从更外层的全局作用域找到 'global' console.log(notDefined); // 报错:ReferenceError, 链上找不到 } inner(); } outer();

一个经典的面试题变形:下面代码的输出是什么?

for (var i = 0; i < 5; i++) { setTimeout(function() { console.log(i); }, 1000); }

答案是连续输出5个5。因为var声明的i属于函数作用域(在这个例子中是全局作用域),循环结束后i的值已经是5setTimeout中的回调函数在1秒后执行,它们共享同一个外部作用域中的变量i,所以都打印5。这引出了闭包和块级作用域的重要性。

2.2 闭包:函数与其词法环境的捆绑

闭包是指一个函数能够记住并访问其词法作用域,即使该函数在其词法作用域之外执行。它是实现数据私有化、模块化和函数柯里化的基础。

利用闭包可以解决上面的循环问题:

for (var i = 0; i < 5; i++) { (function(j) { // 立即执行函数创建了一个新的作用域,参数j捕获了当前循环的i值 setTimeout(function() { console.log(j); // 0, 1, 2, 3, 4 }, 1000); })(i); }

更现代的写法是使用let声明变量,它拥有块级作用域,每次循环都会创建一个新的块级作用域,本质上也是为每次迭代创建了一个新的“闭包环境”:

for (let i = 0; i < 5; i++) { setTimeout(function() { console.log(i); // 0, 1, 2, 3, 4 }, 1000); }

闭包的实际应用与内存管理:闭包会导致外部函数的变量无法被垃圾回收,因为内部函数持有对其的引用。这是一个双刃剑,在需要持久化状态时很有用(如计数器、缓存),但也可能引起内存泄漏。例如,将DOM元素引用存储在闭包中,即使该DOM元素已从页面移除,只要闭包存在,它就无法被回收。

2.3this的动态绑定规则

this的指向是动态的,在函数被调用时才确定。主要有四种绑定规则:

  1. 默认绑定:独立函数调用。非严格模式下,this指向全局对象(浏览器中是window);严格模式下为undefined

    function foo() { console.log(this.a); } var a = 2; foo(); // 2 (非严格模式)
  2. 隐式绑定:函数作为对象的方法被调用。this指向调用该方法的对象。

    var obj = { a: 2, foo: function() { console.log(this.a); } }; obj.foo(); // 2

    隐式丢失是一个常见陷阱:将对象方法赋值给一个变量再调用,会应用默认绑定规则。

    var bar = obj.foo; bar(); // undefined (严格模式下) 或 2 (非严格模式,指向window,window.a未定义)
  3. 显式绑定:使用callapplybind方法强制指定this

    function foo() { console.log(this.a); } var obj = { a: 2 }; foo.call(obj); // 2 var boundFoo = foo.bind(obj); boundFoo(); // 2
  4. new绑定:使用new操作符调用构造函数。this会绑定到新创建的对象实例上。

    function Foo(a) { this.a = a; } var bar = new Foo(2); console.log(bar.a); // 2

优先级new绑定 > 显式绑定 > 隐式绑定 > 默认绑定。

箭头函数的this:箭头函数没有自己的this,它的this继承自外层第一个普通函数的this,且一旦确定就不可更改(callapplybind也无法改变)。这使得箭头函数在需要固定this指向的场景(如回调函数)中非常有用,但也因此不能作为构造函数。

3. 原型、继承与面向对象:JavaScript的“类”系统

JavaScript是一门基于原型的语言,它的“继承”机制与基于类的语言(如Java)有本质不同。理解原型链是理解JavaScript对象系统的关键。

3.1 构造函数、原型对象与实例的关系

每个函数都有一个prototype属性(原型对象),每个对象都有一个__proto__属性(指向其构造函数的原型对象,现已建议使用Object.getPrototypeOf()获取)。

function Person(name) { this.name = name; } // 在Person的原型上添加方法,所有实例共享 Person.prototype.sayHello = function() { console.log('Hello, ' + this.name); }; var person1 = new Person('Alice'); var person2 = new Person('Bob'); person1.sayHello(); // Hello, Alice person2.sayHello(); // Hello, Bob console.log(person1.__proto__ === Person.prototype); // true console.log(Person.prototype.constructor === Person); // true console.log(person1 instanceof Person); // true

关系图(概念示意):

实例 (person1) ├── __proto__ --> Person.prototype └── 自身属性 (name: 'Alice') Person.prototype ├── constructor --> Person函数 └── 共享方法 (sayHello) Person函数 └── prototype --> Person.prototype

3.2 原型链与属性查找

当访问一个对象的属性时,如果对象自身没有,引擎会沿着它的__proto__(即其构造函数的prototype)向上查找,直到找到该属性或到达原型链的尽头(null)。这就是原型链。

console.log(person1.hasOwnProperty('sayHello')); // false, sayHello来自原型 console.log(person1.hasOwnProperty('name')); // true // 查找过程:person1自身 -> Person.prototype -> Object.prototype -> null console.log(person1.toString()); // [object Object], 来自Object.prototype

3.3 实现继承的几种模式

  1. 原型链继承:让子类的原型等于父类的实例。

    function Parent() { this.names = ['kevin', 'daisy']; } function Child() {} Child.prototype = new Parent(); // 关键 var child1 = new Child(); child1.names.push('yayu'); var child2 = new Child(); console.log(child2.names); // ['kevin', 'daisy', 'yayu'] 问题:引用类型属性被所有实例共享!
  2. 构造函数继承:在子类构造函数内部调用父类构造函数。

    function Parent(name) { this.name = name; this.colors = ['red', 'blue']; } function Child(name) { Parent.call(this, name); // 关键 } var child1 = new Child('child1'); child1.colors.push('green'); var child2 = new Child('child2'); console.log(child2.colors); // ['red', 'blue'] 优点:解决了引用属性共享问题。 console.log(child2.name); // 'child2' // 缺点:方法都在构造函数中定义,无法复用。
  3. 组合继承(最常用):结合原型链继承和构造函数继承。

    function Parent(name) { this.name = name; this.colors = ['red', 'blue']; } Parent.prototype.sayName = function() { console.log(this.name); }; function Child(name, age) { Parent.call(this, name); // 第二次调用Parent this.age = age; } Child.prototype = new Parent(); // 第一次调用Parent Child.prototype.constructor = Child; // 修正constructor指向 Child.prototype.sayAge = function() { console.log(this.age); }; var child1 = new Child('kevin', 18); child1.colors.push('green'); console.log(child1.colors); // ['red', 'blue', 'green'] child1.sayName(); // kevin child1.sayAge(); // 18 var child2 = new Child('daisy', 20); console.log(child2.colors); // ['red', 'blue'] // 缺点:调用了两次父类构造函数。
  4. 寄生组合式继承(理想方式):解决组合继承调用两次构造函数的问题。

    function inheritPrototype(child, parent) { var prototype = Object.create(parent.prototype); // 创建父类原型副本 prototype.constructor = child; // 增强对象 child.prototype = prototype; // 指定对象 } function Parent(name) { this.name = name; } Parent.prototype.sayName = function() { /* ... */ }; function Child(name, age) { Parent.call(this, name); // 只调用一次 this.age = age; } inheritPrototype(Child, Parent); Child.prototype.sayAge = function() { /* ... */ };

    这是ES5时代最理想的继承方式。ES6的classextends语法糖,其底层实现原理就是寄生组合式继承。

3.4 ES6 Class 语法糖

class让面向对象编程的语法更清晰,但它本质上仍然是基于原型的。

class Parent { constructor(name) { this.name = name; } sayName() { console.log(this.name); } } class Child extends Parent { constructor(name, age) { super(name); // 相当于 Parent.call(this, name) this.age = age; } sayAge() { console.log(this.age); } } const child = new Child('Alice', 10); child.sayName(); // Alice console.log(child instanceof Parent); // true

注意class中的方法是定义在原型上的,且默认是严格模式。super关键字在构造函数中必须在使用this之前调用。

4. 异步编程与事件循环:从回调地狱到优雅处理

JavaScript是单线程的,异步编程是其核心特性,用于处理I/O、网络请求、定时等不阻塞主线程的任务。理解事件循环(Event Loop)是理解异步代码执行顺序的关键。

4.1 回调函数与“回调地狱”

最初的异步模式是回调函数。

// 模拟一个异步读取文件的操作 function readFile(path, callback) { setTimeout(() => { console.log(`读取 ${path} 完成`); callback(null, `来自 ${path} 的数据`); }, Math.random() * 1000); } // 回调地狱:多层嵌套,难以阅读和维护 readFile('/etc/passwd', function(err, data) { if (err) throw err; console.log(data); readFile('/etc/hosts', function(err, data2) { if (err) throw err; console.log(data2); readFile('/etc/profile', function(err, data3) { if (err) throw err; console.log(data3); // ... 更多嵌套 }); }); });

4.2 Promise:更优雅的异步管理

Promise对象代表一个异步操作的最终完成(或失败)及其结果值。它有三种状态:pending(进行中)、fulfilled(已成功)、rejected(已失败)。

const promise = new Promise((resolve, reject) => { // 异步操作 setTimeout(() => { const success = Math.random() > 0.5; if (success) { resolve('操作成功!'); } else { reject(new Error('操作失败!')); } }, 1000); }); promise .then((result) => { console.log('成功:', result); return result + ' 附加信息'; // 可以返回新值或新的Promise }) .then((newResult) => { console.log('链式调用:', newResult); }) .catch((error) => { console.error('失败:', error); }) .finally(() => { console.log('无论成功失败都会执行'); });

Promise的静态方法

  • Promise.all(iterable):所有Promise都成功时返回结果数组,任何一个失败则立即失败。
  • Promise.race(iterable):第一个 settled(成功或失败)的Promise的结果决定最终结果。
  • Promise.allSettled(iterable):所有Promise都 settled 后,返回一个描述每个结果的对象数组。
  • Promise.any(iterable):任何一个成功即成功,全部失败才失败。

4.3 Generator 与 Async/Await

Generator函数可以暂停执行和恢复执行,是协程在ES6的实现。async/await是Generator函数的语法糖,使得异步代码看起来像同步代码。

// Generator 示例 function* gen() { yield 'hello'; yield 'world'; return 'ending'; } const g = gen(); console.log(g.next()); // { value: 'hello', done: false } console.log(g.next()); // { value: 'world', done: false } console.log(g.next()); // { value: 'ending', done: true } // 用 Generator + Promise 模拟 async/await function readFilePromise(path) { return new Promise(resolve => { setTimeout(() => resolve(`数据来自 ${path}`), 1000); }); } function* readFiles() { const data1 = yield readFilePromise('file1'); console.log(data1); const data2 = yield readFilePromise('file2'); console.log(data2); } // 需要一个执行器来运行 function run(gen) { const g = gen(); function next(data) { const result = g.next(data); if (result.done) return result.value; result.value.then(data => next(data)); } next(); } run(readFiles); // Async/Await (最终方案) async function readFilesAsync() { try { const data1 = await readFilePromise('file1'); console.log(data1); const data2 = await readFilePromise('file2'); console.log(data2); } catch (error) { console.error(error); } } readFilesAsync();

async函数返回一个Promise,await后面通常跟一个Promise,它会暂停async函数的执行,等待Promise settled,然后恢复执行并返回结果。

4.4 事件循环(Event Loop)机制详解

这是面试中的高频难点。JavaScript运行时包含一个调用栈(Call Stack)、一个微任务队列(Microtask Queue)和一个宏任务队列(Macrotask Queue,也叫Task Queue)。

执行顺序规则

  1. 执行全局同步代码(这算一个宏任务)。
  2. 执行过程中,遇到异步任务,根据类型分发:
    • Promise.then/catch/finallyMutationObserverqueueMicrotask产生的任务进入微任务队列
    • setTimeoutsetIntervalsetImmediate(Node)、I/O操作、UI渲染等产生的任务进入宏任务队列
  3. 当前宏任务(即调用栈)清空后,立即检查并清空整个微任务队列(如果执行微任务时又产生了新的微任务,会继续执行,直到微任务队列为空)。
  4. 执行下一个宏任务(从宏任务队列中取一个)。
  5. 循环步骤3-4。

经典面试题:说出下面代码的输出顺序。

console.log('1'); setTimeout(function() { console.log('2'); new Promise(function(resolve) { console.log('3'); resolve(); }).then(function() { console.log('4'); }); }, 0); new Promise(function(resolve) { console.log('5'); resolve(); }).then(function() { console.log('6'); }); console.log('7');

分析过程

  1. 同步代码:输出1,5,7。同时,setTimeout回调(宏任务)入队,Promise.then(微任务)入队。
  2. 当前宏任务(主线程)执行完毕,清空微任务队列:输出6
  3. 取下一个宏任务(setTimeout回调)执行:输出2,3。执行中又产生了一个新的微任务(Promise.then)。
  4. 该宏任务执行完毕,清空微任务队列:输出4

最终输出顺序1, 5, 7, 6, 2, 3, 4

注意async函数中,await之前的代码是同步执行的,await表达式本身会阻塞其后的代码,但await后面的Promise被放入微任务队列。可以简单理解为await fn()等价于Promise.resolve(fn()).then(...)

5. 类型、运算与内置对象:那些容易踩的坑

JavaScript是动态弱类型语言,其类型转换和运算规则常常出人意料,也是面试官喜欢考察的点。

5.1 数据类型与类型判断

七种原始类型Undefined,Null,Boolean,Number,String,Symbol(ES6),BigInt(ES2020)。一种引用类型Object(包括Array,Function,Date,RegExp等)。

类型判断方法

  • typeof:能区分大部分原始类型,但typeof null === 'object'(历史遗留bug),typeof函数返回'function'
  • instanceof:检测构造函数的prototype属性是否出现在对象的原型链上。用于检测自定义对象或内置对象。
  • Object.prototype.toString.call():最准确的类型判断方法。
    Object.prototype.toString.call([]); // '[object Array]' Object.prototype.toString.call(null); // '[object Null]' Object.prototype.toString.call(new Date()); // '[object Date]'
  • Array.isArray():专门判断数组。

5.2 隐式类型转换(强制类型转换)

这是坑最多的地方,发生在运算符、等号、逻辑判断等场景。

=====

  • ===(严格相等):类型和值都必须相同。
  • ==(抽象相等):如果类型不同,会先进行隐式类型转换再比较。
    '' == false // true (都转为数字0) [] == false // true ([]转为字符串'',再转为数字0;false转为数字0) null == undefined // true null == 0 // false (null只与undefined相等) '0' == false // true

+运算符:优先进行字符串连接。

1 + '1' // '11' 1 + true // 2 (true转为1) 1 + undefined // NaN (undefined转为NaN) 1 + null // 1 (null转为0) [] + [] // '' (数组转为字符串) [] + {} // '[object Object]' ([]转为'', {}转为'[object Object]') {} + [] // 0 (这里的{}被解析为代码块, +[] 将[]转为数字0)

其他运算符:通常将操作数转为数字。

'5' - '2' // 3 '5' * '2' // 10 true - false // 1 'abc' - 1 // NaN

逻辑判断中的转换if,&&,||,!会将条件转为布尔值。假值(Falsy)包括:false,0,-0,0n,'',null,undefined,NaN。其余均为真值(Truthy)。

5.3 数组与对象的常用API与陷阱

数组方法

  • 会改变原数组push/pop/shift/unshift,splice,sort,reverse,fill,copyWithin
  • 不改变原数组slice,concat,map,filter,reduce,forEach,find,some,every等。

一个关于sort的坑

[10, 5, 40, 25].sort(); // [10, 25, 40, 5] 默认按字符串Unicode码点排序! [10, 5, 40, 25].sort((a, b) => a - b); // [5, 10, 25, 40] 正确排序

对象遍历

  • for...in:遍历对象自身和继承的可枚举字符串键属性。通常需要配合hasOwnProperty检查。
    const obj = { a: 1, b: 2 }; for (let key in obj) { if (obj.hasOwnProperty(key)) { console.log(key, obj[key]); } }
  • Object.keys():返回对象自身可枚举属性的键名数组
  • Object.values():返回对象自身可枚举属性的值数组
  • Object.entries():返回对象自身可枚举属性的键值对数组

for...offor...in的区别

  • for...of用于遍历可迭代对象(如Array, Map, Set, String, arguments等)的
  • for...in用于遍历对象的键名
const arr = ['a', 'b']; arr.customProp = 'hi'; for (let i in arr) { console.log(i); } // '0', '1', 'customProp' for (let v of arr) { console.log(v); } // 'a', 'b'

5.4 深拷贝与浅拷贝

  • 浅拷贝:只拷贝对象的第一层属性。如果属性是引用类型,拷贝的是引用地址,新旧对象会共享这个引用。
    // 浅拷贝方法 const obj = { a: 1, b: { c: 2 } }; const shallowCopy1 = Object.assign({}, obj); const shallowCopy2 = { ...obj }; // 扩展运算符 shallowCopy1.b.c = 99; console.log(obj.b.c); // 99, 原对象也被修改了!
  • 深拷贝:完全拷贝一个对象,包括其嵌套的引用类型,新旧对象完全独立。
    // 简单深拷贝(有局限) const deepCopySimple = JSON.parse(JSON.stringify(obj)); // 局限:无法处理函数、Symbol、undefined、循环引用,会忽略这些属性或报错。 // 手写一个基础深拷贝函数(递归,处理循环引用需额外逻辑) function deepClone(source, map = new WeakMap()) { if (source === null || typeof source !== 'object') { return source; // 原始类型直接返回 } if (map.has(source)) { return map.get(source); // 解决循环引用 } const target = Array.isArray(source) ? [] : {}; map.set(source, target); for (let key in source) { if (source.hasOwnProperty(key)) { target[key] = deepClone(source[key], map); } } return target; } // 注意:这个基础版本未处理Date、RegExp、Function等特殊对象,生产环境建议使用lodash的_.cloneDeep。

6. DOM、BOM与事件:浏览器环境的交互核心

前端开发离不开与浏览器文档对象模型(DOM)和浏览器对象模型(BOM)的交互。

6.1 DOM操作与性能优化

获取DOM元素是高频操作,选择合适的方法很重要。

// 获取单个元素 document.getElementById('id'); // 最快 document.querySelector('#id .class'); // 灵活,但相对慢 // 获取元素集合(返回NodeList或HTMLCollection,是类数组,非真正的数组) document.getElementsByClassName('class'); // 动态集合 document.getElementsByTagName('div'); document.querySelectorAll('.class'); // 静态集合(推荐) // 创建与插入 const div = document.createElement('div'); div.textContent = 'Hello'; div.className = 'box'; // 插入操作 parent.appendChild(div); // 末尾插入 parent.insertBefore(newNode, referenceNode); // 指定位置前插入 parent.replaceChild(newChild, oldChild); // 替换

性能优化要点

  1. 减少DOM访问:将频繁访问的DOM元素存储在变量中。
  2. 批量操作:使用DocumentFragment或先脱离文档流操作再一次性插入。
    const fragment = document.createDocumentFragment(); for (let i = 0; i < 1000; i++) { const li = document.createElement('li'); li.textContent = `Item ${i}`; fragment.appendChild(li); } document.getElementById('list').appendChild(fragment); // 一次重排重绘
  3. 避免触发同步布局(布局抖动):不要在循环中交替读取和修改样式,这会导致浏览器反复计算布局。
    // 不好:读写交替 for (let i = 0; i < boxes.length; i++) { boxes[i].style.width = boxes[i].offsetWidth + 10 + 'px'; // 读 offsetWidth 触发重排 } // 好:先读后写 const widths = []; for (let i = 0; i < boxes.length; i++) { widths[i] = boxes[i].offsetWidth; } for (let i = 0; i < boxes.length; i++) { boxes[i].style.width = widths[i] + 10 + 'px'; }

6.2 事件流:捕获与冒泡

DOM事件流包括三个阶段:捕获阶段(从window向下到目标元素)、目标阶段冒泡阶段(从目标元素向上到window)。

// addEventListener 第三个参数 element.addEventListener('click', handler, true); // 捕获阶段触发 element.addEventListener('click', handler, false); // 冒泡阶段触发(默认) element.addEventListener('click', handler, { capture: true, once: true, passive: true }); // 选项对象

事件委托:利用事件冒泡,将事件监听器设置在父元素上,通过event.target来定位实际触发事件的子元素。这对于动态添加的子元素或大量子元素非常高效。

<ul id="parent-list"> <li>// 创建自定义事件 const myEvent = new CustomEvent('myevent', { detail: { // 可以通过detail传递自定义数据 message: 'Hello from custom event!', time: new Date() }, bubbles: true, // 是否冒泡 cancelable: true // 是否可取消 }); // 监听自定义事件 const elem = document.getElementById('myElement'); elem.addEventListener('myevent', function(event) { console.log('Custom event received:', event.detail.message); }); // 触发自定义事件 elem.dispatchEvent(myEvent);

6.4 BOM常用对象

  • window:浏览器窗口的全局对象。
  • location:包含当前URL信息。location.href,location.search,location.hash
  • history:操作浏览器会话历史。history.back(),history.forward(),history.go(n),history.pushState(),history.replaceState()(用于前端路由)。
  • navigator:浏览器信息。navigator.userAgent,navigator.platform
  • screen:屏幕信息。screen.width,screen.height
  • localStorage/sessionStorage:本地存储。

关于window.history.back():它等同于用户点击浏览器的后退按钮。在单页应用(SPA)中,通常需要监听popstate事件来响应路由变化,而不是直接依赖back()的同步行为。

7. 模块化、ES6+新特性与代码组织

随着前端项目日益复杂,模块化成为必备技能。ES6引入了官方的模块化标准。

7.1 模块化演进:从IIFE到ES Module

  1. IIFE(立即执行函数表达式):早期的模块化手段,利用函数作用域创建私有空间。

    var Module = (function() { var privateVar = 'hidden'; function privateMethod() { /* ... */ } return { publicMethod: function() { console.log(privateVar); } }; })(); Module.publicMethod(); // 'hidden' // Module.privateVar // undefined
  2. CommonJS:Node.js的模块规范,使用require导入,module.exports导出。同步加载,适用于服务器端。

    // math.js function add(a, b) { return a + b; } module.exports = { add }; // 或 exports.add = add; // main.js const math = require('./math.js'); console.log(math.add(2, 3));
  3. AMD (Asynchronous Module Definition):异步模块定义,代表是RequireJS。适用于浏览器端。

    // 定义模块 define(['dependency'], function(dependency) { return { method: function() { dependency.doSomething(); } }; }); // 加载模块 require(['module'], function(module) { module.method(); });
  4. ES Module (ESM):ES6语言标准模块系统。现代浏览器和Node.js(需.mjs后缀或package.json中设置"type": "module")原生支持。

    // math.mjs 或 math.js (在支持ESM的环境中) export function add(a, b) { return a + b; } export const PI = 3.14159; // 或 export default function add() { ... } // main.mjs import { add, PI } from './math.mjs'; // 或 import * as math from './math.mjs'; // 或 import add from './math.mjs'; (对应export default) console.log(add(2, 3), PI);

    ESM特性:静态解析(编译时确定依赖)、异步加载、支持循环依赖、输出的是值的只读引用(CommonJS输出的是值的拷贝)。

7.2 常用ES6+新特性速览

  • let/const:块级作用域变量声明。const用于声明常量(对于对象,其引用不可变,但属性可变)。
  • 箭头函数(params) => { expression }。无自己的thisargumentssupernew.target,不能作为构造函数。
  • 模板字符串`Hello, ${name}!`,支持多行字符串和嵌入表达式。
  • 解构赋值
    const [a, b] = [1, 2]; const { name, age } = person; const { data: userInfo } = response; // 重命名
  • 扩展运算符与剩余参数
    const arr1 = [1, 2]; const arr2 = [...arr1, 3, 4]; // [1, 2, 3, 4] function sum(...numbers) { /* numbers是数组 */ } const { a, ...rest } = obj; // 剩余属性
  • 默认参数function greet(name = 'Guest') { }
  • Map&Set
    const map = new Map(); map.set('key', 'value'); map.get('key'); const set = new Set([1, 2, 2, 3]); // [1, 2, 3]
  • Promise&async/await:(前面已详述)
  • Symbol:唯一的值,常用于定义对象的唯一属性键。
  • Proxy&Reflect:元编程,拦截并定义对象的基本操作。
    const handler = { get(target, prop) { console.log(`Getting ${prop}`); return Reflect.get(...arguments); } }; const proxy = new Proxy({a: 1}, handler); proxy.a; // 输出: Getting a
  • 可选链操作符(?.obj?.prop,如果objnullundefined,则返回undefined,而不是报错。
  • 空值合并运算符(??a ?? b,仅在anullundefined时返回b
  • BigInt:用于表示任意精度的整数。const bigNum = 9007199254740991n;

7.3 代码组织与设计模式

理解模块化后,在组织代码时,一些经典的设计模式能提升代码的可维护性和复用性。

  • 单例模式:确保一个类只有一个实例。
    const Singleton = (function() { let instance; function createInstance() { return { /* ... */ }; } return { getInstance: function() { if (!instance) { instance = createInstance(); } return instance; } }; })(); const s1 = Singleton.getInstance(); const s2 = Singleton.getInstance(); console.log(s1 === s2); // true
  • 观察者模式/发布-订阅模式:对象间一对多的依赖关系,当一个对象状态改变,所有依赖它的对象都会得到通知。事件系统就是典型的观察者模式。
  • 工厂模式:不暴露创建对象的具体逻辑,而是用一个工厂方法来创建对象。
  • 策略模式:定义一系列算法,将它们封装起来,并且使它们可以相互替换。

在实际项目中,结合ES Module和这些设计模式,可以构建出清晰、松耦合、易于测试的代码结构。例如,将不同的功能(如网络请求、状态管理、工具函数)拆分成独立的模块,通过导入导出进行组合,是现代前端工程化的基础。

http://www.jsqmd.com/news/1380650/

相关文章:

  • 基于Z3定理证明器构建模型查找器:自动化逻辑约束求解实践
  • Ontology 本体论是什么?从哲学概念到 AI、知识图谱与软件工程
  • 2026年优选河北省秦皇岛市海港区信誉好的Ai承制品牌 - 装修教育财税推荐2026
  • Apache Doris BitMap去重实战:高基数场景下替代COUNT(DISTINCT)的性能优化方案
  • 8.12总结
  • 预测模型反馈闭环:别把所有差评都归因于模型
  • 买铸铝门避坑指南:如何挑选靠谱的铸铝门厂家 - 门业测评
  • Docker服务启动失败排查与systemd配置覆盖实战指南
  • Vulkan着色器数据映射机制与性能优化实践
  • 统计报告规范核查:核查统计结果报告是否规范:缺自由度/样本量、p值与显著性表述不一致、未报效应量、检验名缺失等。
  • 基于Deer-Go框架构建多智能体协作系统:从架构设计到实战调优
  • Claude Code技能生态解析与十大必装技能推荐
  • AI工程师革命:驯服Agent的六步法则
  • 可白嫖源码---课程设计--毕业设计--springboot校园足球社团管理平台[编号:project08798](案例分析)
  • 3分钟上手!MisakaHookFinder:Galgame文本提取终极指南,打破语言障碍
  • TronWeb终极指南:5分钟构建你的首个TRON区块链应用
  • Ubuntu 22.04安装微信QQ:基于Deepin-Wine的完整方案与优化指南
  • 55-工厂6S落地工具体系化构建研究:全流程五大阶段与七大核心工具 | 杨逢昌
  • 2026跨境电商找GEO优化厂家全攻略:合规选型标准、避坑FAQ与核心服务商实力盘点 - 行业观察网
  • 2026年度优选专业北京公司团建服务商强力推荐柠檬团建 - 装修教育财税推荐2026
  • 计算机毕业设计之高校学习帮扶网站
  • 武汉短视频、短剧公司为什么必须办广电许可证?条件 + 时效全解析 - 招小财
  • 深度解析青岛建设厅网站:一站式查询政策解读与办事指南的最佳入口指南
  • BugKuCTF-WEB超详细解题思路(21-30)
  • ReactNative拖拽排序组件在鸿蒙平台的适配与优化
  • 2026年房地产行业找GEO优化厂家:选型标准避坑指南,附头部服务商实力盘点与适配解析 - 产业观察报
  • 如何用OpenSpeedy免费游戏加速工具提升10倍游戏体验?终极指南
  • AI Agent架构解析:从LLM大脑到Harness调度,构建智能工作流
  • 机器学习系列:高斯混合模型(2)
  • 2026企业福利礼品服务商优选指南:五大品牌深度实测与决策框架 - 品牌报告