热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

如何理解Node.js模块化

本篇内容主要讲解“如何理解Node.js模块化”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“如何理解No

本篇内容主要讲解“如何理解Node.js模块化”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“如何理解Node.js模块化”吧!

正文

在Node.js中,内置了两个模块来进行模块化管理,这两个模块也是两个我们非常熟悉的关键字:require和module。内置意味着我们可以在全局范围内使用这两个模块,而无需像其他模块一样,需要先引用再使用。

无需 require('require') or require('module')  

在Node.js中引用一个模块并不是什么难事儿,很简单:

const config = require('/path/to/file')  

但实际上,这句简单的代码执行了一共五个步骤:

如何理解Node.js模块化

了解这五个步骤有助于我们了解Node.js模块化的基本原理,也能让我们甄别一些陷阱,让我们简单概括下这五个步骤都做了什么:

  •  Resolving: 找到待引用的目标模块,并生成绝对路径。

  •  Loading: 判断待引用的模块内容是什么类型,它可能是.json文件、.js文件或者.node文件。

  •  Wrapping: 顾名思义,包装被引用的模块。通过包装,让模块具有私有作用域。

  •  Evaluating: 被加载的模块被真正的解析和处理执行。

  •  Caching: 缓存模块,这让我们在引入相同模块时,不用再重复上述步骤。

有些同学看完这五个步骤可能已经心知肚明,对这些原理轻车熟路,有些同学心中可能产生了更多疑惑,无论如何,接下来的内容会详细解析上述的执行步骤,希望能帮助大家答疑解惑 or 巩固知识、查缺补漏。

By the way,如果有需要,可以和我一样,构建一个实验目录,跟着Demo进行实验。

什么是模块

想要了解模块化,需要先直观地看看模块是什么。

我们知道在Node.js中,文件即模块,刚刚提到了模块可以是.js、.json或者.node文件,通过引用它们,可以获取工具函数、变量、配置等等,但是它的具体结构是怎样呢?在命令行中简单执行下面的命令就可以看到模块,也就是module对象的结构:

~/learn-node $ node  > module  Module {    id: '',    exports: {},    parent: undefined,    filename: null,    loaded: false,    children: [],    paths: [ ... ] }  

可以看到模块也就是一个普通对象,只不过结构中有几个特殊的属性值,需要我们一一去理解,有些属性,例如id、parent、filename、children甚至都无需解释,通过字面意思就可以理解。

后续的内容会帮助大家理解这些字段的意义和作用。

Resolving

大致了解了什么是模块后,我们从第一个步骤Resolving开始,了解模块化原理,也就是Node.js如何寻找目标模块,并生成目标模块的绝对路径。

那么什么我们刚刚要先打印module对象,先让大家了解module的结构呢?因为这里有两个字段值id、paths和Resolving这个步骤息息相关。一起来看看吧。

  •  首先是id属性:

每个module都有id属性,通常这个属性值是模块的完整路径,通过这个值Node.js可以标识和定位模块的所在位置。但是在这儿并没有具体的模块,我们只是在命令行中输出了module的结构,所以为默认的值(repl表示交互式解释器)。

  •  其次是paths属性:

这个paths属性有什么作用呢?Node.js允许我们用多种方式来引用模块,比如相对路径、绝对路径、预置路径(马上会解释),假设我们需要引用一个叫做find-me的模块,require如何帮助我们找到这个模块呢?

require('find-me')  

我们先打印看看paths中是什么内容:

~/learn-node $ node  > module.paths  [ '/Users/samer/learn-node/repl/node_modules',    '/Users/samer/learn-node/node_modules',    '/Users/samer/node_modules',    '/Users/node_modules',    '/node_modules',    '/Users/samer/.node_modules',    '/Users/samer/.node_libraries',    '/usr/local/Cellar/node/7.7.1/lib/node' ]  

ok,其实就是一堆系统绝对路径,这些路径表示了所有目标模块可能出现的位置,并且它们是有序的,这意味着Node.js会按序查找paths中列出的所有路径,如果找到这个模块,就输出该模块的绝对路径供后续使用。

现在我们知道Node.js会在这一堆目录中查找module,尝试执行require('find-me')来查找find-me模块,由于我们并没有在任何目录放置find-me模块,所以Node.js在遍历所有目录之后并不能找到目标模块,因此报错Cannot find module 'find-me',这个错误大家也许经常看到:

~/learn-node $ node  > require('find-me')  Error: Cannot find module 'find-me'      at Function.Module._resolveFilename (module.js:470:15)      at Function.Module._load (module.js:418:25)      at Module.require (module.js:498:17)      at require (internal/module.js:20:19)      at repl:1:1      at ContextifyScript.Script.runInThisContext (vm.js:23:33)      at REPLServer.defaultEval (repl.js:336:29)      at bound (domain.js:280:14)      at REPLServer.runBound [as eval] (domain.js:293:12)      at REPLServer.onLine (repl.js:533:10)  

现在,可以尝试把需要引用的find-me模块放在上述的任意一个目录下,在这里我们创建一个node_modules目录,并创建find-me.js文件,让Node.js能够找到它:

~/learn-node $ mkdir node_modules   ~/learn-node $ echo "console.log('I am not lost');" > node_modules/find-me.js  ~/learn-node $ node  > require('find-me');  I am not lost  {}  >  

手动创建了find-me.js文件后,Node.js果然找到了目标模块。当然,当Node.js本地的node_modules目录中找到了find-me模块,就不会再去后续的目录中继续寻找了。

有Node.js开发经验的同学会发现在引用模块时,不一定非得指定到准确的文件,也可以通过引用目录来完成对目标模块的引用,例如:

~/learn-node $ mkdir -p node_modules/find-me  ~/learn-node $ echo "console.log('Found again.');" > node_modules/find-me/index.js  ~/learn-node $ node  > require('find-me');  Found again.  {}  >  

find-me目录下的index.js文件会被自动引入。

当然,这是有规则限制的,Node.js之所以能够找到find-me目录下的index.js文件,是因为默认的模块引入规则是当具体的文件名缺失时寻找index.js文件。我们也可以更改引入规则(通过修改package.json),比如把index \-> main:

~/learn-node $ echo "console.log('I rule');" > node_modules/find-me/main.js  ~/learn-node $ echo '{ "name": "find-me-folder", "main": "main.js" }' > node_modules/find-me/package.json  ~/learn-node $ node  > require('find-me'); I rule  {}  > 

require.resolve

如果你只想要在项目中引入某个模块,而不想立即执行它,可以使用require.resolve方法,它和require方法功能相似,只是并不会执行被引入的模块方法:

> require.resolve('find-me');  '/Users/samer/learn-node/node_modules/find-me/start.js'  > require.resolve('not-there');  Error: Cannot find module 'not-there'      at Function.Module._resolveFilename (module.js:470:15)      at Function.resolve (internal/module.js:27:19)      at repl:1:9      at ContextifyScript.Script.runInThisContext (vm.js:23:33)      at REPLServer.defaultEval (repl.js:336:29)      at bound (domain.js:280:14)      at REPLServer.runBound [as eval] (domain.js:293:12)      at REPLServer.onLine (repl.js:533:10)      at emitOne (events.js:101:20)      at REPLServer.emit (events.js:191:7)  >  

可以看到,如果该模块被找到了,Node.js会打印模块的完整路径,如果未找到,就报错。

了解了Node.js是如何寻找模块之后,来看看Node.js是如何加载模块的。

模块间的父子依赖关系

我们把模块间引用关系,表示为父子依赖关系。

简单创建一个lib/util.js文件,添加一行console.log语句,标识这是一个被引用的子模块。

~/learn-node $ mkdir lib  ~/learn-node $ echo "console.log('In util');" > lib/util.js  

在index.js也输入一行console.log语句,标识这是一个父模块,并引用刚刚创建的lib/util.js作为子模块。

~/learn-node $ echo "require('./lib/util'); console.log('In index, parent', module);" > index.js  

执行index.js,看看它们间的依赖关系:

~/learn-node $ node index.js  In util  In index  Module {    id: '.',    path: '/Users/samer/',    exports: {},    parent: null,    filename: '/Users/samer/index.js',    loaded: false,    children: [      Module {        id: '/Users/samer/lib/util.js',        path: '/Users/samer/lib',        exports: {},        parent: [Circular *1],        filename: '/Users/samer/lib/util.js',        loaded: true,        children: [],        paths: [Array]      }    ],    paths: [...]  }  

在这里我们关注与依赖关系相关的两个属性:children和parent。

在打印的结果中,children字段包含了被引入的util.js模块,这表明了util.js是index.js所依赖的子模块。

但仔细观察util.js模块的parent属性,发现这里出现了Circular这个值,原因是当我们打印模块信息时,产生了循环的依赖关系,在子模块信息中打印父模块信息,又要在父模块信息中打印子模块信息,所以Node.js简单地将它处理标记为Circular。

为什么需要了解父子依赖关系呢?因为这关系到Node.js是如何处理循环依赖关系的,后续会详细描述。

在看循环依赖关系的处理问题之前,我们需要先了解两个关键的概念:exports和module.exports。

exports, module.exports

  • exports:

exports是一个特殊的对象,它在Node.js中可以无需声明,作为全局变量直接使用。它实际上是module.exports的引用,通过修改exports可以达到修改module.exports的目的。

exports也是刚刚打印的module结构中的一个属性值,但是刚刚打印出来的值都是空对象,因为我们并没有在文件中对它进行操作,现在我们可以尝试简单地为它赋值:

// 在lib/util.js的开头新增一行  exports.id = 'lib/util';  // 在index.js的开头新增一行  exports.id = 'index';  

执行index.js:

~/learn-node $ node index.js  In index Module {    id: '.',    exports: { id: 'index' },    loaded: false,    ... }  In util Module {    id: '/Users/samer/learn-node/lib/util.js',    exports: { id: 'lib/util' },    parent:     Module {       id: '.',       exports: { id: 'index' },       loaded: false,       ... },    loaded: false,    ... }  

可以看到刚刚添加的两个id属性被成功添加到exports对象中。我们也可以添加除id以外的任意属性,就像操作普通对象一样,当然也可以把exports变成一个function,例如:

exports = function() {}  
  •  module.exports:

module.exports对象其实就是我们最终通过require所得到的东西。我们在编写一个模块时,最终给module.exports赋什么值,其他人引用该模块时就能得到什么值。例如,结合刚刚对lib/util的操作:

const util = require('./lib/util');  console.log('UTIL:', util);  // 输出结果 UTIL: { id: 'lib/util' }  

由于我们刚刚通过exports对象为module.exports赋值{id: 'lib/util'},因此require的结果就相应地发生了变化。

现在我们大致了解了exports和module.exports都是什么,但是有一个小细节需要注意,那就是Node.js的模块加载是个同步的过程。

我们回过头来看看module结构中的loaded属性,这个属性标识这个模块是否被加载完成,通过这个属性就能简单验证Node.js模块加载的同步性。

当模块被加载完成后,loaded值应该为true。但到目前为止每次我们打印module时,它的状态都是false,这其实正是因为在Node.js中,模块的加载是同步的,当我们还未完成加载的动作(加载的动作包括对module进行标记,包括标记loaded属性),因此打印出的结果就是默认的loaded: false。

我们用setImmediate来帮助我们验证这个信息:

// In index.js  setImmediate(() => {    console.log('The index.js module object is now loaded!', module)  });  
The index.js module object is now loaded! Module {    id: '.',    exports: [Function],    parent: null,    filename: '/Users/samer/learn-node/index.js',    loaded: true,    children:     [ Module {         id: '/Users/samer/learn-node/lib/util.js',         exports: [Object],         parent: [Circular],         filename: '/Users/samer/learn-node/lib/util.js',         loaded: true,         children: [],         paths: [Object] } ],    paths:     [ '/Users/samer/learn-node/node_modules',       '/Users/samer/node_modules',       '/Users/node_modules',       '/node_modules' ] }  

ok,由于console.log被后置到加载完成(打完标记)之后,因此现在加载状态变成了loaded: true。这充分验证了Node.js模块加载是一个同步过程。

了解了exports、module.exports以及模块加载的同步性后,来看看Node.js是如何处理模块的循环依赖关系。

模块循环依赖

在上述内容中,我们了解到了模块之间是存在父子依赖关系的,那如果模块之间产生了循环的依赖关系,Node.js会怎么处理呢?假设有两个模块,分别为module1.js和modole2.js,并且它们互相引用了对方,如下:

// lib/module1.js  exports.a = 1; require('./module2'); // 在这儿引用  exports.b = 2;  exports.c = 3;  // lib/module2.js  const Module1 = require('./module1');  console.log('Module1 is partially loaded here', Module1); // 引用module1并打印它  

尝试运行module1.js,可以看到输出结果:

~/learn-node $ node lib/module1.js  Module1 is partially loaded here { a: 1 }  

结果中只输出了{a: 1},而{b: 2, c: 3}却不见了。仔细观察module1.js,发现我们在module1.js的中间位置添加了对module2.js的引用,也就是exports.b = 2和exports.c = 3还未执行之前的位置。如果我们把这个位置称作发生循环依赖的位置,那么我们得到的结果就是在循环依赖发生前被导出的属性,这也是基于我们上述验证过的Node.js的模块加载是同步过程的结论。

Node.js就是这样简单地处理循环依赖。在加载模块的过程中,会逐步构建exports对象,为exports赋值。如果我们在模块被完全加载前就引用这个模块,那么我们只能得到部分的exports对象属性。

.json和.node

在Node.js中,我们不仅能用require来引用Javascript文件,还能用于引用JSON或C++插件(.json和.node文件)。我们甚至都不需要显式地声明对应的文件后缀。

在命令行中也可以看到require所支持的文件类型:

~ % node  > require.extensions  [Object: null prototype] {    '.js': [Function (anonymous)],    '.json': [Function (anonymous)],    '.node': [Function (anonymous)]  }  

当我们用require引用一个模块,首先Node.js会去匹配是否有.js文件,如果没有找到,再去匹配.json文件,如果还没找到,最后再尝试匹配.node文件。但是通常情况下,为了避免混淆和引用意图不明,可以遵循在引用.json或.node文件时显式地指定后缀,引用.js时省略后缀(可选,或都加上后缀)。

  •  .json文件:

引用.json文件很常用,例如一些项目中的静态配置,使用.json文件来存储更便于管理,例如:

{    "host": "localhost",    "port": 8080  }  

引用它或使用它都很简单:

const { host, port } = require('./config');  console.log(`Server will run at http://${host}:${port}`)  

输出如下:

Server will run at http://localhost:8080  
  •  .node文件:

.node文件是由C++文件转化而来,官网提供了一个简单的由C++实现的 hello插件 ,它暴露了一个hello()方法,输出字符串world。有需要的话,可以跳转链接做更多了解并进行实验。

我们可以通过node-gyp来将.cc文件编译和构建成.node文件,过程也非常简单,只需要配置一个binding.gyp文件即可。这里不详细阐述,只需要知道生成.node文件后,就可以正常地引用该文件,并使用其中的方法。

例如,将hello()转化生成addon.node文件后,引用并使用它:

const addon = require('./addon');  console.log(addon.hello());  

Wrapping

其实在上述内容中,我们阐述了在Node.js中引用一个模块的前两个步骤Resolving和Loading,它们分别解决了模块的路径和加载的问题。接下来看看Wrapping都做了什么。

Wrapping就是包装,包装的对象就是所有我们在模块中写的代码。也就是我们引用模块时,其实经历了一层『透明』的包装。

要了解这个包装过程,首先要理解exports和module.exports之间的区别。

exports是对module.exports的引用,我们可以在模块中使用exports来导出属性,但是不能直接替换它。例如:

exports.id = 42; // ok,此时exports指向module.exports,相当于修改了module.exports.  exports = { id: 42 }; // 无用,只是将它指向了{ id: 42 }对象而已,对module.exports不会产生实际改变.  module.exports = { id: 42 }; // ok,直接操作module.exports.  

大家也许会有疑惑,为什么这个exports对象似乎对每个模块来说都是一个全局对象,但是它又能够区分导出的对象是来自于哪个模块,这是怎么做到的。

在了解包装(Wrapping)过程之前,来看一个小例子:

// In a.js  var value = 'global'  // In b.js  console.log(value) // 输出:global  // In c.js console.log(value) // 输出:global  // In index.html  ...        

当我们在a.js脚本中定义一个值value,这个值是全局可见的,后续引入的b.js和c.js都是可以访问该value值。但是在Node.js模块中却并不是这样,在一个模块中定义的变量具有私有作用域,在其它模块中无法直接访问。这个私有作用域如何产生的?

答案很简单,是因为在编译模块之前,Node.js将模块中的内容包装在了一个function中,通过函数作用域实现了私有作用域。

通过require('module').wrapper可以打印出wrapper属性:

~ $ node  > require('module').wrapper  [ '(function (exports, require, module, __filename, __dirname) { ',    '\n});' ]  >  

Node.js不会直接执行文件中的任何代码,但它会通过这个包装后的function来执行代码,这让我们的每个模块都有了私有作用域,不会互相影响。

这个包装函数有五个参数:exports, require, module, __filename, __dirname。我们可以通过arguments参数直接访问和打印这些参数:

~/learn-node $ echo "console.log(arguments)" > index.js  ~/learn-node $ node index.js  { '0': {},    '1':     { [Function: require]       resolve: [Function: resolve],       main:        Module {          id: '.',          exports: {},          parent: null,          filename: '/Users/samer/index.js',          loaded: false,          children: [],          paths: [Object] },       extensions: { ... },       cache: { '/Users/samer/index.js': [Object] } },    '2':     Module {       id: '.',       exports: {},       parent: null,       filename: '/Users/samer/index.js',       loaded: false,       children: [],       paths: [ ... ] },    '3': '/Users/samer/index.js',    '4': '/Users/samer' }  

简单了解一下这几个参数,第一个参数exports初始时为空(未赋值),第二、三个参数require和module是和我们引用的模块相关的实例,它们俩不是全局的。第四、五个参数__filename和__dirname分别表示了文件路径和目录。

整个包装后的函数所做的事儿约等于:

function (require, module, __filename, __dirname) {    let exports = module.exports;      // Your Code...      return module.exports;  }  

总而言之,wrapping就是将我们的模块作用域私有化,以module.exports作为返回值将变量或方法暴露出来,以供使用。

Cache

缓存很容易理解,通过一个案例来看看吧:

echo 'console.log(`log something.`)' > index.js  // In node repl  > require('./index.js')  log something.  {}  > require('./index.js')  {}  >  

可以看到,两次引用同一个模块,只打印了一次信息,这是因为第二次引用时取的是缓存,无需重新加载模块。

打印require.cache可以看到当前的缓存信息:

> require.cache  [Object: null prototype] {    '/Users/samer/index.js': Module {      id: '/Users/samer/index.js',      path: '/Users/samer/',      exports: {},      parent: Module {        id: '',        path: '.',        exports: {},        parent: undefined,        filename: null,        loaded: false,        children: [Array],        paths: [Array]      },      filename: '/Users/samer/index.js',      loaded: true,      children: [],      paths: [        '/Users/samer/learn-node/repl/node_modules',        '/Users/samer/learn-node/node_modules',        '/Users/samer/node_modules',        '/Users/node_modules',        '/node_modules',        '/Users/samer/.node_modules',        '/Users/samer/.node_libraries',        '/usr/local/Cellar/node/7.7.1/lib/node'      ]    }  }  

可以看到刚刚引用的index.js文件处于缓存当中,因此不会重新加载模块。当然我们也可以通过删除require.cache来清空缓存内容,达到重新加载的目的,这里不再演示。

到此,相信大家对“如何理解Node.js模块化”有了更深的了解,不妨来实际操作一番吧!这里是编程笔记网站,更多相关内容可以进入相关频道进行查询,关注我们,继续学习!


推荐阅读
  • 本文介绍了如何使用 Node.js 和 Express(4.x 及以上版本)构建高效的文件上传功能。通过引入 `multer` 中间件,可以轻松实现文件上传。首先,需要通过 `npm install multer` 安装该中间件。接着,在 Express 应用中配置 `multer`,以处理多部分表单数据。本文详细讲解了 `multer` 的基本用法和高级配置,帮助开发者快速搭建稳定可靠的文件上传服务。 ... [详细]
  • 在JavaWeb开发中,文件上传是一个常见的需求。无论是通过表单还是其他方式上传文件,都必须使用POST请求。前端部分通常采用HTML表单来实现文件选择和提交功能。后端则利用Apache Commons FileUpload库来处理上传的文件,该库提供了强大的文件解析和存储能力,能够高效地处理各种文件类型。此外,为了提高系统的安全性和稳定性,还需要对上传文件的大小、格式等进行严格的校验和限制。 ... [详细]
  • 技术分享:使用 Flask、AngularJS 和 Jinja2 构建高效前后端交互系统
    技术分享:使用 Flask、AngularJS 和 Jinja2 构建高效前后端交互系统 ... [详细]
  • Web开发框架概览:Java与JavaScript技术及框架综述
    Web开发涉及服务器端和客户端的协同工作。在服务器端,Java是一种优秀的编程语言,适用于构建各种功能模块,如通过Servlet实现特定服务。客户端则主要依赖HTML进行内容展示,同时借助JavaScript增强交互性和动态效果。此外,现代Web开发还广泛使用各种框架和库,如Spring Boot、React和Vue.js,以提高开发效率和应用性能。 ... [详细]
  • Webpack 初探:Import 和 Require 的使用
    本文介绍了 Webpack 中 Import 和 Require 的基本概念和使用方法,帮助读者更好地理解和应用模块化开发。 ... [详细]
  • 网站访问全流程解析
    本文详细介绍了从用户在浏览器中输入一个域名(如www.yy.com)到页面完全展示的整个过程,包括DNS解析、TCP连接、请求响应等多个步骤。 ... [详细]
  • 单元测试:使用mocha和should.js搭建nodejs的单元测试
    2019独角兽企业重金招聘Python工程师标准BDD测试利器:mochashould.js众所周知对于任何一个项目来说,做好单元测试都是必不可少 ... [详细]
  • 如果应用程序经常播放密集、急促而又短暂的音效(如游戏音效)那么使用MediaPlayer显得有些不太适合了。因为MediaPlayer存在如下缺点:1)延时时间较长,且资源占用率高 ... [详细]
  • 本文详细介绍了在编写jQuery插件时需要注意的关键要点,包括模块化支持、命名规范和性能优化等内容,旨在帮助开发者提高插件的质量和可维护性。 ... [详细]
  • Spark与HBase结合处理大规模流量数据结构设计
    本文将详细介绍如何利用Spark和HBase进行大规模流量数据的分析与处理,包括数据结构的设计和优化方法。 ... [详细]
  • Framework7:构建跨平台移动应用的高效框架
    Framework7 是一个开源免费的框架,适用于开发混合移动应用(原生与HTML混合)或iOS&Android风格的Web应用。此外,它还可以作为原型开发工具,帮助开发者快速创建应用原型。 ... [详细]
  • MySQL Decimal 类型的最大值解析及其在数据处理中的应用艺术
    在关系型数据库中,表的设计与SQL语句的编写对性能的影响至关重要,甚至可占到90%以上。本文将重点探讨MySQL中Decimal类型的最大值及其在数据处理中的应用技巧,通过实例分析和优化建议,帮助读者深入理解并掌握这一重要知识点。 ... [详细]
  • ### 优化后的摘要本学习指南旨在帮助读者全面掌握 Bootstrap 前端框架的核心知识点与实战技巧。内容涵盖基础入门、核心功能和高级应用。第一章通过一个简单的“Hello World”示例,介绍 Bootstrap 的基本用法和快速上手方法。第二章深入探讨 Bootstrap 与 JSP 集成的细节,揭示两者结合的优势和应用场景。第三章则进一步讲解 Bootstrap 的高级特性,如响应式设计和组件定制,为开发者提供全方位的技术支持。 ... [详细]
  • Python 伦理黑客技术:深入探讨后门攻击(第三部分)
    在《Python 伦理黑客技术:深入探讨后门攻击(第三部分)》中,作者详细分析了后门攻击中的Socket问题。由于TCP协议基于流,难以确定消息批次的结束点,这给后门攻击的实现带来了挑战。为了解决这一问题,文章提出了一系列有效的技术方案,包括使用特定的分隔符和长度前缀,以确保数据包的准确传输和解析。这些方法不仅提高了攻击的隐蔽性和可靠性,还为安全研究人员提供了宝贵的参考。 ... [详细]
  • 在 Axublog 1.1.0 版本的 `c_login.php` 文件中发现了一个严重的 SQL 注入漏洞。该漏洞允许攻击者通过操纵登录请求中的参数,注入恶意 SQL 代码,从而可能获取敏感信息或对数据库进行未授权操作。建议用户尽快更新到最新版本并采取相应的安全措施以防止潜在的风险。 ... [详细]
author-avatar
石pimentel_958
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有