63. JavaScript 模块化
-
什么是 JavaScript 模块化?
- 模块化 是指将代码分割成独立的模块,每个模块负责一个单一的功能或逻辑。通过模块化,代码更加结构化、易于维护、复用性强。
- 在 JavaScript 中,模块化的实现经历了多个阶段,从最早的全局作用域到后来的 AMD、CommonJS 和 ES6 模块系统。
-
常见的模块化方案:
-
CommonJS:Node.js 中使用的模块化方案,模块通过
module.exports
导出,通过require
导入。- 特点:同步加载,适用于服务器端。
- 例子:
// 导出模块 module.exports = function add(a, b) {return a + b; }; // 导入模块 const add = require('./add'); console.log(add(2, 3)); // 输出 5
-
AMD (Asynchronous Module Definition):一种前端模块化方案,主要用于浏览器环境,模块通过
define
定义,通过require
导入。- 特点:异步加载,适用于浏览器端。
- 例子:
define(['math'], function(math) {console.log(math.add(2, 3)); });
-
ES6 模块:ES6 标准引入的模块系统,通过
export
导出,通过import
导入。- 特点:支持静态分析,异步加载,适用于浏览器和服务器。
- 例子:
// 导出模块 export function add(a, b) {return a + b; } // 导入模块 import { add } from './math'; console.log(add(2, 3)); // 输出 5
-
64. 原型链
-
什么是原型链?
- 原型链 (Prototype Chain) 是 JavaScript 中实现继承的机制。每个对象都有一个
[[Prototype]]
属性(可以通过__proto__
访问),指向它的原型对象。原型对象也可以有自己的原型,形成一个链条,直到指向null
为止。 - 当访问一个对象的属性时,如果对象本身没有这个属性,JavaScript 引擎会沿着原型链向上查找,直到找到该属性或到达链条末端
null
。
- 原型链 (Prototype Chain) 是 JavaScript 中实现继承的机制。每个对象都有一个
-
例子:
function Person(name) {this.name = name; } Person.prototype.greet = function() {console.log('Hello, ' + this.name); };const alice = new Person('Alice'); alice.greet(); // 输出 "Hello, Alice" console.log(alice.__proto__ === Person.prototype); // true
-
原型链的常见面试问题:
-
instanceof
的工作原理是什么?instanceof
运算符会沿着对象的原型链查找,判断对象的原型链中是否有构造函数的prototype
属性。如果有,则返回true
。- 例子:
console.log(alice instanceof Person); // true
-
原型链继承的缺点是什么?
- 所有实例共享同一个原型对象上的属性或方法,如果原型对象上有引用类型的属性,可能会导致实例之间相互影响。
- 例子:
function Animal() {this.traits = ['fur']; } Animal.prototype.getTraits = function() {return this.traits; };const cat = new Animal(); const dog = new Animal(); cat.traits.push('claws'); console.log(dog.getTraits()); // 输出 ["fur", "claws"]
-
65. 浏览器缓存
-
浏览器缓存的机制有哪些?
浏览器缓存是提升性能的重要手段,通过将资源缓存到本地,避免重复下载相同的资源。常见的缓存机制有两种:强缓存 和 协商缓存。-
强缓存:浏览器根据缓存规则,直接从缓存中读取资源,不与服务器进行通信。常见的 HTTP 头包括:
Expires
:设置资源的过期时间,使用的是服务器时间,缺点是时间同步问题。Cache-Control
:更灵活的缓存控制方式,可以使用max-age
指定资源的最大缓存时间。
-
协商缓存:浏览器在请求资源时,会与服务器进行验证,服务器会根据资源是否更新来决定是返回新的资源还是使用缓存。常见的 HTTP 头包括:
Last-Modified
/If-Modified-Since
:通过文件的最后修改时间来验证资源是否更新。ETag
/If-None-Match
:通过文件的唯一标识符来验证资源是否更新,优先级高于Last-Modified
。
- 缓存控制的例子:
Cache-Control: max-age=31536000, public ETag: "abc123"
-
66. DOM 操作
- 如何高效地操作 DOM?
-
批量操作 DOM:尽量减少直接操作 DOM 的次数,使用文档片段 (
DocumentFragment
) 或缓存 DOM 元素来优化性能。- 例子:
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);
- 例子:
-
使用
innerHTML
代替createElement
:对于创建较简单的 HTML 结构,使用innerHTML
相较于createElement
性能更高。- 例子:
document.getElementById('list').innerHTML = '<li>Item 1</li><li>Item 2</li>';
- 例子:
-
减少回流和重绘:尽量减少对 DOM 树结构的频繁修改,特别是涉及布局属性的修改(如宽高、位置等),这些操作会触发回流(重排)和重绘。
- 例子:
const el = document.getElementById('box'); el.style.width = '100px'; el.style.height = '100px'; el.style.backgroundColor = 'blue'; // 这三行操作会触发多次回流
- 例子:
-
事件代理:对于动态生成的元素,可以通过事件代理的方式提高性能,而不是给每个元素都绑定事件。
- 例子:
document.getElementById('list').addEventListener('click', function(event) {if (event.target.tagName === 'LI') {console.log('Item clicked:', event.target.textContent);} });
- 例子:
-
67. Vue 响应式原理
-
Vue 的响应式系统是如何工作的?
Vue 的响应式系统基于 数据劫持 和 发布-订阅模式 来实现。它通过使用Object.defineProperty
对数据进行劫持,在数据发生变化时触发视图更新。- 数据劫持:当 Vue 实例初始化时,Vue 会递归地遍历数据对象的每一个属性,并使用
Object.defineProperty
将这些属性转换为 getter 和 setter,从而拦截对这些属性的访问和修改。 - 依赖收集:当组件渲染时,Vue 会将渲染过程中使用到的数据属性进行依赖收集,并建立依赖关系,当数据发生变化时,会通知对应的观察者(watcher)重新渲染视图。
- 发布-订阅模式:当数据发生变化时,setter 会触发依赖更新,执行观察者的更新方法,达到数据驱动视图更新的效果。
- 示例代码:
let data = { name: 'Alice' }; Object.defineProperty(data, 'name', {get() {console.log('获取 name');return name;},set(newVal) {console.log('设置 name 为', newVal);name = newVal;} });console.log(data.name); // 触发 get data.name = 'Bob'; // 触发 set
- 数据劫持:当 Vue 实例初始化时,Vue 会递归地遍历数据对象的每一个属性,并使用