JavaScript面试题手册
nodejs如何开启多进程,进程之间如何通讯?
在Node.js中,可以利用cluster模块开启多个进程,以此来充分地利用多核CPU的资源。cluster模块可以创建一组Node.js进程,它们共享同一个服务器端口。以下是一个使用cluster模块的基本步骤:导入cluster模块和其他必须的模块。使用cluster.isMaster判断当前是否是主进程(Master)。在主进程中,可以使用cluster.fork()来创建工作进程(Worker)。在工作进程中,执行实际的应用代码,如HTTP服务器的监听等。监听相应的事件来处理工作进程的启动、在线、退出等情况。进程之间的通信可以通过以下方式:主进程通过worker.send()发送消息到工作进程。工作进程通过process.send()发送消息到主进程。监听message事件来接收消息。下面是一个创建多个工作进程并实现主工作进程通信的简单示例:const cluster = require('cluster');const http = require('http');const numCPUs = require('os').cpus().length;if (cluster.isMaster) { console.log(`主进程 ${process.pid} 正在运行`); // 衍生工作进程。 for (let i = 0; i < numCPUs; i++) { const worker = cluster.fork(); // 主进程接收工作进程发送的消息 worker.on('message', (msg) => { console.log(`主进程收到消息 '${msg}' 来自工作进程 ${worker.process.pid}`); }); } cluster.on('exit', (worker, code, signal) => { console.log(`工作进程 ${worker.process.pid} 已退出`); });} else { // 工作进程可以共享任何TCP连接。 // 在本例中,它是一个 HTTP 服务器。 http.createServer((req, res) => { res.writeHead(200); res.end('你好世界\n'); // 工作进程向主进程发送消息 process.send(`工作进程 ${process.pid} 收到请求`); }).listen(8000); console.log(`工作进程 ${process.pid} 已启动`);}在这个例子中,主进程创建了和CPU核心数量相同的工作进程,并设置了接受消息的监听器。每当工作进程接收到HTTP请求时,它就会向主进程发送一个消息。主进程监听到消息后,在控制台中输出相关信息。这种模式让Node.js应用可以在多核CPU上以更高效的方式运行,而且主工作进程之间的消息传递机制让它们可以交换信息。
阅读 53·2024年6月24日 16:43
Webpack 有哪些优化手段
Webpack优化手段概览Webpack是一个现代JavaScript应用程序的静态模块打包器,它可以帮助开发者管理和打包他们的前端资源。以下是Webpack的一些常见优化手段:1. Tree ShakingTree Shaking是一个通过删除未使用代码来减少打包体积的过程。Webpack内置支持ES6模块的Tree Shaking,可以识别出未被引用的代码并在打包时排除它们。例子:在开发过程中,可能会引入一个库,比如Lodash,但只使用其中的几个函数。通过配置sideEffects属性为false,Webpack可以标记并移除那些未被使用的模块,减小最终的bundle体积。2. 代码分割 (Code Splitting)代码分割允许将代码分解为可按需加载的多个包,从而减少单个包的大小,提高加载速度。例子:使用import()语法实现动态导入,将特定功能模块分割成独立的chunk,只有当用户需要时才加载这些模块。3. 使用Externals当你使用一些CDN外部扩展或从外部引入库时,可以配置Webpack的externals选项,让Webpack知道这些依赖不应该打包进bundle。例子:例如,如果你的项目使用jQuery,可以从CDN引入而不是打包到bundle中,配置externals让Webpack忽略它。4. 优化解析配置resolve选项可以加快模块解析速度。例如,通过配置extensions减少文件尝试的后缀列表,设置alias提供路径别名减少查找路径的时间。例子:resolve: { extensions: ['.js', '.jsx'], alias: { Components: path.resolve(__dirname, 'src/components/') }}5. 使用缓存Webpack的cache选项可以启用持久化缓存,提高重建速度。例子:在webpack.config.js中启用cache选项,使得模块在第一次构建后将转换结果缓存起来,之后的构建会加快。6. 压缩代码利用插件如TerserPlugin压缩JavaScript代码,减少文件大小。例子:在optimization配置中使用TerserPlugin来开启代码压缩。7. 优化CSS使用如MiniCssExtractPlugin和cssnano等工具将CSS提取为单独的文件并压缩。例子:plugins: [ new MiniCssExtractPlugin({ filename: '[name].[contenthash].css', })],optimization: { minimizer: [ new CssMinimizerPlugin(), ],},8. 使用持久化缓存通过设置output.filename使用内容哈希,当文件内容未变化时,利用浏览器缓存机制避免重新下载。例子:output: { filename: '[name].[contenthash].js', path: path.resolve(__dirname, 'dist')}9. 使用高效的加载器和插件例如babel-loader的cacheDirectory选项,或者HappyPack插件来并行处理任务。例子:module: { rules: [ { test: /\.js$/, use: 'babel-loader?cacheDirectory=true', exclude: /node_modules/ } ]}10. 监控和分析使用webpack-bundle-analyzer等工具分析bundle大小,找到优化点。例子:通过安装并配置`webpack-bundle-analyzer
阅读 31·2024年6月24日 16:43
前端模块规范有哪些?模块如何异步加载?
前端模块规范在JavaScript中,模块规范主要有以下几种:CommonJS:CommonJS是Node.js采用的模块规范,它通过 require函数来同步加载依赖的其他模块,通过 module.exports或 exports对象来导出模块。例子: // math.js function add(a, b) { return a + b; } module.exports = { add }; // main.js const math = require('./math'); console.log(math.add(1, 2));AMD (Asynchronous Module Definition):AMD是RequireJS实现的规范。它支持在浏览器环境中异步加载模块,使用 define函数定义模块,使用 require函数加载模块。例子: // 定义模块math.js define(function() { return { add: function(a, b) { return a + b; } }; }); // 加载模块 require(['math'], function(math) { console.log(math.add(1, 2)); });ES Modules (ESM):ECMAScript 2015(也称为ES6)引入了官方的JavaScript模块标准,这是现代前端开发中推荐使用的模块化解决方案。它支持静态导入和导出,也可以配合特定的语法进行动态导入。例子: // math.js export function add(a, b) { return a + b; } // main.js import { add } from './math.js'; console.log(add(1, 2));UMD (Universal Module Definition):UMD旨在提供一个跨平台的模块定义方式,使得一个模块可以同时在AMD和CommonJS环境中运行。例子: (function(root, factory) { if (typeof define === 'function' && define.amd) { // AMD环境 define([], factory); } else if (typeof exports === 'object') { // CommonJS环境 module.exports = factory(); } else { // 浏览器全局变量 root.myModule = factory(); } }(this, function() { return { add: function(a, b) { return a + b; } }; }));模块的异步加载使用AMD规范(如RequireJS):AMD规范设计之初就考虑了模块的异步加载。通过 require函数,我们可以异步加载所需的模块。使用ES Modules的动态 import():ES6模块支持使用 import()函数来实现模块的动态导入,这使得我们可以在代码执行时按需加载模块。例子: // 假设我们有一个math.js模块 // 动态导入ES模块 import('./math.js').then(math => { console.log(math.add(1, 2)); }).catch(error => { console.log('Error loading the module:', error); });Webpack等构建工具的代码分割(Code Splitting)功能:现代前端构建工具像Webpack提供了代码分割功能,可以将应用程序分割成多个chunks,并在需要时异步加载。例子:在Webpack中,可以通过动态 import()语法来创建所谓的"分割点"(split point)。 // Webpack代码分割示例 import(/* webpackChunkName: "math" */ './math').then(math => { console.log(math.add(1, 2)); });
阅读 21·2024年6月24日 16:43
addEventListener 和 attachEvent 的区别是什么?
addEventListener 和 attachEvent 都是用于在 Web 开发中将事件监听器绑定到 DOM 元素上的方法,但它们之间存在一些关键差异:浏览器兼容性:addEventListener 是 DOM Level 2 Events 规范的一部分,它被所有现代浏览器(包括 IE9 及以上版本)支持。attachEvent 是微软为 IE8 及以下版本的浏览器提出的一个方法,不是标准的 DOM 事件处理方法。语法:addEventListener 采用以下语法: javascript element.addEventListener(event, function, useCapture); 其中 event 是事件名称(不带 on 前缀),function 是事件处理函数,而 useCapture 是一个布尔值,用于指定事件是在捕获阶段处理还是在冒泡阶段处理。attachEvent 采用不同的语法: javascript element.attachEvent(eventWithOn, function); 其中 eventWithOn 是带有 on 前缀的事件名称,function 是事件处理函数。事件流:addEventListener 允许你指定事件处理函数是在捕获阶段还是冒泡阶段被调用。attachEvent 只支持事件冒泡,不支持事件捕获。this 关键字:在 addEventListener 中绑定的事件处理函数里,this 指向绑定事件的元素。在 attachEvent 中绑定的事件处理函数里,this 指向全局对象 window,而不是绑定事件的元素。多个事件监听器:使用 addEventListener,你可以为同一个事件添加多个事件监听器,它们会按顺序执行。而 attachEvent 有可能会导致多个事件监听器的执行顺序不一致,而且同一个事件处理函数如果添加多次,也会被执行多次。移除事件监听器:addEventListener 配对的是 removeEventListener,可以用于移除事件监听器。attachEvent 配对的是 detachEvent,用于移除事件监听器,但需要确保传递给 detachEvent 的参数与 attachEvent 时的参数完全一致。由于 attachEvent 方法是一个非标准方法且仅在老版本的 IE 浏览器中可用,所以通常推荐使用 addEventListener,因为它是跨浏览器兼容的并遵循现代的 Web 标准。当需要支持老版本 IE 浏览器时,开发者可能需要编写额外的代码来兼容 attachEvent。
阅读 24·2024年6月24日 16:43
页面意外崩溃,这时候 JS 线程都已经崩溃了,如何传递通知呢?
在Web应用中,当JavaScript线程崩溃导致页面无法正常工作时,确实需要一种机制来通知用户或者开发者。在这种情况下,由于主JavaScript线程已经崩溃,传统的错误捕获如 try-catch或者 window.onerror事件监听可能都不起作用。但是,我们仍然可以采用以下几种策略:1. 使用Web WorkersWeb Workers运行在与主JavaScript线程分离的后台线程中。即使主线程崩溃,Web Workers可能仍然保持运行状态。因此,可以在页面加载时,启动一个监控Worker,用来检测主线程的心跳。如果主线程心跳停止(例如,可以通过设置定时消息来实现心跳),Worker可以尝试通知服务器或者更新UI来告诉用户发生了错误。2. 利用 window.onunload和 window.onbeforeunload事件可以在 window.onunload或 window.onbeforeunload事件中进行错误上报。如果浏览器支持 navigator.sendBeacon方法,即使在页面卸载的情况下也可以向服务器发送数据。虽然这两个事件不是为错误处理设计的,但它们可以用于在页面关闭时发送一些信息,可能包括崩溃通知。3. 使用Service WorkersService Workers作为一种在浏览器后台运行的脚本,可以用来拦截和缓存网络请求,推送通知等。如果设置了Service Worker,并且页面发生崩溃的情况下,它仍然能够接收到fetch请求或者推送事件,从而可以实现一定程度上的错误处理或状态报告。4. 外部心跳系统通过外部系统定时检测Web应用的状态,例如可以通过服务器定时发送请求到客户端,检测页面的响应。如果在预定时间内没有收到响应,或者收到错误响应,服务器则可以记录这类事件并采取相应的措施。5. 自动化监控和错误上报工具使用像Sentry、LogRocket这样的第三方错误监控服务,它们可以帮助在JavaScript出现未捕获异常时自动上报错误。虽然在某些崩溃情况下这些工具也可能失效,但它们仍然是一种有效的自动监控手段。6. 客户端存储在客户端使用如localStorage或sessionStorage,可以在检测到问题时写入状态标志。然后在页面重载或重新打开时检查这些标志,如果发现有异常状态,可以采取相应的通知措施。7. 使用全局异常处理器在可能的情况下,可以注册全局异常处理器 window.addEventListener('error', function() {...}),尽管在某些崩溃情况下可能不会被调用,但它提供了一个捕获异常并尝试通知的机会。示例举一个使用Web Workers进行心跳检测的简单示例:假设我们在主线程中有一个定期运行的函数,模拟心跳:function sendHeartbeat() { if (worker) { worker.postMessage('heartbeat'); }}// 定期发送心跳到WorkersetInterval(sendHeartbeat, 5000);
阅读 36·2024年6月24日 16:43
JavaScript 如何使用 setTimeout 模拟实现 setInterval?
使用 setTimeout 模拟实现 setInterval 的基本思路是:在 setTimeout 的回调函数中再次调用 setTimeout,这样可以不断地延迟执行相同的操作,形成类似 setInterval 的效果。不过,值得注意的是,使用这种方法可以更精确地控制下一次执行的时间,因为你可以在当前任务结束后再设置下一次执行,这样就不会受到之前任务执行时间的影响。下面是一个模拟 setInterval 的示例函数 simulateSetInterval:function simulateSetInterval(callback, interval) { // 用来清除定时器的函数,在simulateSetInterval返回的对象上调用clear方法即可停止。 let timer = { clear: function() { clearTimeout(this.timeoutId); } }; // 这是一个递归函数,用于模拟重复间隔执行 const repeat = function() { callback(); timer.timeoutId = setTimeout(repeat, interval); }; // 开始执行 timer.timeoutId = setTimeout(repeat, interval); // 返回一个控制对象,可以用来停止间隔执行 return timer;}// 使用 simulateSetInterval 的例子let counter = 0;const exampleTimer = simulateSetInterval(() => { console.log('Hello World!'); counter++; if (counter >= 5) { exampleTimer.clear(); console.log('Timer stopped after 5 iterations.'); }}, 1000);在这个例子中,simulateSetInterval 函数接受一个回调函数 callback 和一个间隔时间 interval。函数内部定义了一个递归的 repeat 函数,它首先执行传入的 callback,然后使用 setTimeout 来延迟下一次执行 repeat 函数本身,从而达到周期执行的效果。返回的 timer 对象包含一个 clear 方法,可以用来清除定时器,停止进一步的执行。这种模拟实现方式的好处是,它更加灵活,可以根据任务的实际执行时间动态调整间隔,而 setInterval 在一些情况下可能会导致任务之间的间隔不准确,尤其是在某些任务执行时间较长时。使用 simulateSetInterval,下一次任务的开始时刻总是在当前任务完成后按照设定的间隔进行计时。
阅读 35·2024年6月24日 16:43
attribute和property的区别是什么
HTML文档中,attribute(属性)和property(属性)这两个术语经常使用,它们在不同的上下文中有不同的含义。 对象导向编程中的Attribute和Property在对象导向编程(OOP)的上下文中,attribute和property通常指代与对象关联的数据,但它们的概念和用途有所不同。Attribute (属性):在OOP中,attribute通常指的是对象的内部状态,它们是类定义中的变量。这些是对象的数据成员,用于存储对象的信息。例如,假设我们有一个 Car类,那么 color和 model可能是 Car对象的attributes。Property (属性):Property在OOP中通常指的是提供对attribute的访问的一种特殊方法,这些方法通常是通过getter和setter方法暴露的。Property允许封装attribute,从而可以在读取或修改attribute时添加附加的逻辑,如验证或事件触发。例如,Car类可能有一个 mileage属性,它通过getter方法 get_mileage()和setter方法 set_mileage(value)来访问和修改里程信息,而不是直接公开一个 mileage attribute。HTML文档处理中的Attribute和Property在HTML和Web开发的上下文中,attribute和property也有不同的含义。Attribute (属性):HTML attribute是HTML标签的一部分,用于在HTML文档中为元素定义特定的配置或行为。Attributes在HTML源代码中明确定义,例如 <input type="text" value="Hello">中的 type和 value。Attributes的值通常是在页面加载时定义的静态值。Property (属性):HTML元素在浏览器中表示为JavaScript对象,这些对象具有properties。这些properties与JavaScript运行时相连,表示DOM元素的当前状态。Properties可以在运行时动态改变,例如,通过JavaScript改变input元素的 value property,document.getElementById('myInput').value = 'New Value';。示例:考虑HTML中的一个 <input>元素,其初始HTML可能如下所示:<input id="myInput" type="text" value="Initial">这里,id、type和 value都是HTML attributes。当页面加载后,我们可以通过JavaScript访问 <input>元素的property,比如:var inputElement = document.getElementById('myInput');console.log(inputElement.value); // 输出: Initial在这个时候,value attribute和 value property都是“Initial”。然而,如果用户在input框中输入新的文本,比如“Hello”,那么 value property将改变,而 value attribute仍然为“Initial”。
阅读 21·2024年6月24日 16:43
JavaScript 的继承方式有哪些?
JavaScript的继承方式主要包括以下几种:1. 原型链继承 (Prototype Chain Inheritance)原型链继承是JavaScript最基本的继承方式。每个JavaScript对象都有一个原型(prototype),对象从其原型继承属性和方法。创建子类时,将子类的原型设置为父类的实例,从而实现继承。function Parent() { this.parentProperty = true;}Parent.prototype.getParentProperty = function() { return this.parentProperty;};function Child() { this.childProperty = false;}Child.prototype = new Parent(); // 设置Child的原型为Parent的实例Child.prototype.constructor = Child; // 修正constructor指向var childInstance = new Child();console.log(childInstance.getParentProperty()); // true原型链继承存在的问题包括:创建子类实例时不能向父类构造函数传参,而且所有实例共享父类构造函数中的引用属性。2. 构造函数继承 (Constructor Inheritance)构造函数继承使用父类构造函数来增强子类实例,通过 call或 apply方法在子类构造函数中调用父类构造函数。function Parent(name) { this.name = name; this.colors = ['red', 'blue', 'green'];}function Child(name) { Parent.call(this, name); // 子类构造函数中调用父类构造函数}var childInstance = new Child('ChildName');console.log(childInstance.name); // ChildName这种方式允许不同的子类实例有不同的属性。不过,它无法继承父类原型上定义的方法,因此不是真正意义上的继承。3. 组合继承 (Combination Inheritance)组合继承结合了原型链和构造函数的技术,通过使用原型链继承原型上的属性和方法,使用构造函数继承实例属性。function Parent(name) { this.name = name; this.colors = ['red', 'blue', 'green'];}Parent.prototype.sayName = function() { return this.name;};function Child(name, age) { Parent.call(this, name); // 借用构造函数继承属性 this.age = age;}Child.prototype = new Parent(); // 原型链继承方法Child.prototype.constructor = Child;var childInstance = new Child('ChildName', 18);console.log(childInstance.sayName()); // ChildNameconsole.log(childInstance.age); // 18组合继承弥补了原型链和构造函数继承的不足,然而它也使得父类构造函数被调用两次,造成了一些不必要的性能开销。4. 原型式继承 (Prototypal Inheritance)原型式继承是指使用一个已有对象作为新创建对象的原型。ES5引入了 Object.create方法来实现原型式继承。var parent = { name: 'Parent', sayName: function() { return this.name; }};var child = Object.create(parent);child.name = 'Child';console.log(child.sayName()); // Child这种方式非常简洁,但同样,所有实例会共享引用属性。5. 寄生式继承 (Parasitic Inheritance)寄生式继承类似于原型式继承,但是在此基础上可以增加更多属性或方法。var parent = { name: 'Parent', sayName: function() { return this.name; }};function createAnother(original) { var clone = Object.create(original); clone.sayHi = function() { console.log('Hi'); }; return clone;}var child = createAnother(parent);child.sayHi(); // Hi
阅读 22·2024年6月24日 16:43
JavaScript 如何实现自定义事件吗?
在JavaScript中实现自定义事件主要有以下几个步骤:创建自定义事件:您可以使用Event构造器或者更专门化的构造器如CustomEvent来创建一个事件。CustomEvent构造器还允许您传递附加的信息(称为“详情”或detail属性)。触发自定义事件:使用dispatchEvent方法可以在特定的元素上触发自定义事件。调用此方法时,您需要传递您创建的事件对象。监听自定义事件:使用addEventListener方法可以在一个元素上添加一个事件监听器,当特定的事件类型被触发时执行回调函数。以下是一个简单的例子:// 第一步:创建一个自定义事件// 这里我们创建一个名为 "userLogin" 的事件,并携带一些用户信息var loginEvent = new CustomEvent("userLogin", { detail: { username: "JohnDoe" }});// 第二步:监听这个自定义事件// 假设我们希望在文档的某个元素上监听这个事件document.addEventListener("userLogin", function(e) { console.log("用户登录事件被触发,登录用户名为:" + e.detail.username);});// 第三步:触发自定义事件// 当需要的时候,我们可以在任何时候触发这个自定义事件document.dispatchEvent(loginEvent);在这个例子中,我们定义了一个名为"userLogin"的自定义事件,它在用户登录时被触发。当这个事件被触发时,我们添加了一个监听器来处理这个事件,并输出了用户的名字。这样,我们就可以在应用程序的任何部分触发"用户登录"事件,并且相关的处理逻辑会被执行。这种机制非常有用,特别是在需要在不相关的组件之间传递信息或者在特定的行为之后触发一系列的行为时。使用自定义事件可以使我们的应用程序更加模块化,事件的生产者与消费者可以松耦合地交互。
阅读 24·2024年6月24日 16:43
什么是柯里化函数?JavaScript 中有哪些使用场景?
柯里化(Currying)是一种在函数式编程中常见的技巧。它是指将一个多参数的函数转换成多个单参数(或较少参数)函数的序列。这样做的主要目的是参数复用、提前确认和延迟执行等。定义以 JavaScript 为例,柯里化的一个基本定义可以是这样的:function curry(fn) { return function curried(...args) { if (args.length >= fn.length) { return fn.apply(this, args); } else { return function(...args2) { return curried.apply(this, args.concat(args2)); } } };}这个 curry 函数接收一个函数 fn 作为参数,并返回一个新的函数 curried。新函数检查接收的参数数量,如果足够执行原函数,则直接执行;如果不够,则返回一个新的函数,等待接收更多的参数。使用场景参数复用:当你有一个函数需要多次调用,但是某些参数在这些调用中是不变的时候,可以通过柯里化创建特定的函数,只传入剩下的参数。例如,如果你有一个 add 函数,可以创建一个 addFive 函数,它将一个数与5相加。 function add(a, b) { return a + b; } const addFive = curry(add)(5); addFive(10); // 返回 15延迟计算/执行:柯里化允许你将多个参数的获取分散到多个步骤中。只有当所有需要的参数都被提供后,函数才会被执行。这在需要等待某些数据才能执行操作的情况下非常有用。例如,如果一个函数需要从几个不同的数据源获取数据,可以将每个数据源的结果逐一传入柯里化函数中。动态生成函数:可以动态地生成需要的函数。在处理事件监听或者回调函数的时候,如果某些参数是已知的,可以通过柯里化生成一个新的函数,而无需重新定义函数体。 const on = curry(function(eventType, element, callback) { element.addEventListener(eventType, callback); }); const onClick = on('click'); onClick(document.getElementById('myButton'), () => console.log('Button clicked!'));函数组合:在函数式编程中,柯里化可以帮助实现函数组合。通过柯里化,可以轻松地将一个函数的输出作为另一个函数的输入。这在创建数据流和中间件等链式操作时非常有用。 const compose = (f, g) => (a) => f(g(a)); const multiplyBy2 = (n) => n * 2; const addTen = (n) => n + 10; const multiplyBy2AndAddTen = compose(addTen, multiplyBy2); multiplyBy2AndAddTen(5); // 返回 20总之,柯里化是一种强大的函数式编程技术,它在 JavaScript 中可以用于创建更灵活和可重用的代码。通过柯里化,可以更容易地实现参数复用、延迟执行和高阶函数构建等高级编程模式。
阅读 35·2024年6月24日 16:43