モジュールは Node.js の非常に基本的かつ重要な概念です。さまざまなネイティブ クラス ライブラリがモジュールを通じて提供され、サードパーティのライブラリもモジュールを通じて管理および参照されます。この記事ではモジュールの基本原則から始めて、最終的にはこの原則を使用して簡単なモジュール読み込みメカニズムを自分で実装します。つまり、require
を自分で実装します。
Node は JavaScript と commonjs モジュールを使用し、パッケージ マネージャーとして npm/yarn を使用します。
[ビデオチュートリアルの推奨: node js チュートリアル]
簡単な例
古いルール、原理を説明する前に簡単な例を見てみましょう。この例は、原則を段階的に深く理解することから始まります。 Node.js で何かをエクスポートしたい場合は、module.exports
を使用する必要があります。module.exports
を使用すると、文字列、関数、およびオブジェクト、配列など。まず、最も単純な hello world
:<pre class='brush:php;toolbar:false;'>// a.js
module.exports = "hello world";</pre>
をエクスポートする
を構築し、次に、 function : <pre class='brush:php;toolbar:false;'>// b.js
function add(a, b) {
return a + b;
}
module.exports = add;</pre>
次に、それらを
で使用します。つまり、require
関数によって返される結果は次のとおりです。対応するファイル module.exports
の値: <pre class='brush:php;toolbar:false;'>// index.js
const a = require(&#39;./a.js&#39;);
const add = require(&#39;./b.js&#39;);
console.log(a); // "hello world"
console.log(add(1, 2)); // b导出的是一个加法函数,可以直接使用,这行结果是3</pre>
requireはターゲットファイルを最初に実行します特定のモジュールを
するとき、
module.exports をそのまま使用するのではなく、このファイルを最初から実行します。module.exports = XXX
は実際には 1 行のコードです。後で説明しますが、このコード行の効果は、実際にはモジュール内の exports
属性を変更することです。たとえば、別の c.js
を考えてみましょう。 <pre class='brush:php;toolbar:false;'>// c.js
let c = 1;
c = c + 1;
module.exports = c;
c = 6;</pre>
c.js
では、
をエクスポートしました。この c
いくつかの計算ステップを経て、module.exports = c;
行まで実行すると、c
の値は 2
となるため、require
c.js
の値は 2
であり、後で c
の値を 6
に変更しても、前のコード行には影響しません。 :<pre class='brush:php;toolbar:false;'>const c = require(&#39;./c.js&#39;);
console.log(c); // c的值是2</pre>
前の c.js
の変数
は基本的なデータ型であるため、次の c = 6;
は影響しません。前の module.exports
は、参照型の場合はどうなるでしょうか?直接試してみましょう: <pre class='brush:php;toolbar:false;'>// d.js
let d = {
num: 1
};
d.num++;
module.exports = d;
d.num = 6;</pre>
次に、index.js
him: <pre class='brush:php;toolbar:false;'>const d = require(&#39;./d.js&#39;);
console.log(d); // { num: 6 }</pre>
それは module で見つかりました。
d
の d.num
への割り当ては引き続き有効です。実際、参照型の場合、その値は module.exports
の後だけでなく、モジュールの外側でも変更できます。たとえば、index.js
内で直接変更できます。 :<pre class='brush:php;toolbar:false;'>const d = require(&#39;./d.js&#39;);
d.num = 7;
console.log(d); // { num: 7 }</pre>
require
と
は黒魔術ではありません
前の例からわかるように、require
and
module.exports 私たちが行うことは複雑ではありません。まず、グローバル オブジェクト {}
があると仮定しましょう。最初は空です。require
を実行すると、このコード行を実行すると、ファイルが取り出されて実行されます。このファイルに module.exports
が存在する場合、このコード行を実行すると、module.exports
の値が取得されます。 exports がこのオブジェクトに追加されます。キーは対応するファイル名です。最終的に、オブジェクトは次のようになります:
{ "a.js": "hello world", "b.js": function add(){}, "c.js": 2, "d.js": { num: 2 } }
特定のファイルを再度
require したとき, このオブジェクトに対応する値がある場合は、その値が直接返されます。そうでない場合は、前の手順を繰り返し、ターゲット ファイルを実行して、その module.exports をグローバル オブジェクトに追加し、発信者に返します。このグローバル オブジェクトは、実際にはよく耳にするキャッシュです。
つまり、require
と module.exports には黒魔術はありません。単に実行してターゲット ファイルの値を取得し、それをキャッシュに追加して、取得するだけです。必要なときに出します。
このオブジェクトをもう一度見てください。d.js
は参照型であるため、この参照を取得した場所でその値を変更できます。モジュールの値を変更したくない場合は、 , Object.freeze()、
Object.defineProperty() などのメソッドを使用するなど、モジュールを自分で記述する必要がある場合は、これを処理する必要があります。
モジュールのタイプとロード順序
ファイル モジュール
です。まとめると、主に次の 2 つがあります。種類。 :###
- 組み込みモジュール:
fs
、http
など、Node.js によってネイティブに提供される関数です。これらのモジュールはノード内にあります。.js プロセスは開始時にロードされます。- ファイル モジュール: 前に作成したモジュールとサードパーティ モジュール、つまり
node_modules
以下のモジュールはすべてファイル モジュールです。
ロード順序
ロード順序とは、require(X)
の場合に #XX## を探す順序を指します。 #, 公式ドキュメントに 詳細な疑似コード
が記載されていますが、まとめると、
組み込みモジュールが存在する場合でも、最初に組み込みモジュールをロードするという順序になります。同じ名前のファイルがある場合は、組み込みモジュールを使用することが優先されます。フォルダーの読み込み
- これは組み込みモジュールではないため、最初にキャッシュに移動して見つけてください。
- キャッシュがない場合は、対応するパスを持つファイルを探します。
- 対応するファイルが存在しない場合、このパスはフォルダーとしてロードされます。
- 対応するファイルやフォルダーが見つからない場合は、
node_modules- に移動して探してください。
エラーが見つからなかった場合でも、エラーを報告しました。
前述したように、ファイルが見つからない場合はフォルダーを探しますが、フォルダー全体を読み込むことは不可能です。フォルダーを読み込むときも同様です。読み込みシーケンスは次のとおりです:
まず、このフォルダーの下にrequirepackage.json
- があるかどうかを確認します。存在する場合は、
package.jsonmain
フィールド内のmain
フィールドに値がある場合、対応するファイルをロードします。したがって、一部のサードパーティ ライブラリのソース コードを見て入り口が見つからない場合は、package.json
のmain
フィールド (例:) を見てください。 jquery
のmain
フィールドは次のようになります:"main": "dist/jquery.js"
。
- が存在しない場合、または
package.json
にmain
がない場合は、index## を探します。 # ファイル。
これら 2 つのステップのどちらも見つからない場合は、エラーが報告されます。
- #サポートされるファイル タイプ
主に次の 3 つのファイル タイプをサポートします:
##。 js
.js実は、原理については以前に詳しく説明しましたが、ここからがハイライトであり、自分たちで実装することになります ## #必要とする###。ファイルは最も一般的に使用されるファイル タイプです。ロード時、最初に JS ファイル全体が実行され、次に前述の
- module.exports が実行されます。 require
ファイルは通常のテキスト ファイルです。の戻り値として使用されます。
.json
:
.json- JSON.parse を使用してオブジェクトに変換し、返します。それ。
ファイルは C でコンパイルされたバイナリ ファイルです。通常、純粋なフロントエンドがこのタイプに接触することはほとんどありません。.node
:
.node手書き
require
require の実装は、実際には Node.js 全体のモジュール読み込みメカニズムを実装することです。解決する必要がある問題を見てみましょう:
受信したパス名ドキュメントを通じて、対応するモジュールを確認します。
見つかったファイルを実行し、同時に
module
requireNode.js モジュールの読み込み関数はすべてメソッドと属性を挿入して、モジュール ファイルを使用できるようにします。
- Return モジュールの
- module.exports
この記事の手書きコードはすべて Node.js 公式ソース コードと関数名を参照しています。変数名と変数名は可能な限り統一してください。実際にはソースコードの簡易版です。比較してください。具体的な方法を書き留める際には、対応するソースコードのアドレスも掲載します。コード全体は次のファイルにあります:
- https://github.com/nodejs/node/blob/c6b96895cc74bc6bd658b4c6d5ea152d6e686d20/lib/internal/modules/cjs/loader.js
Module class
Module クラスにあります。コード全体ではオブジェクト指向の考え方が使用されています。JS オブジェクト指向にあまり詳しくない場合は、次のことができます。最初にお読みください この記事を読んでください
。 Moduleクラスのコンストラクタも複雑ではありません。主にいくつかの値を初期化します。正式な
Module名と区別するために、独自のクラスは次のようになります。 MyModule: という名前<pre class='brush:php;toolbar:false;'>function MyModule(id = &#39;&#39;) {
this.id = id; // 这个id其实就是我们require的路径
this.path = path.dirname(id); // path是Node.js内置模块,用它来获取传入参数对应的文件夹路径
this.exports = {}; // 导出的东西放这里,初始化为空对象
this.filename = null; // 模块对应的文件名
this.loaded = false; // loaded用来标识当前模块是否已经加载
}</pre><h3 id="require方法">require方法</h3>
<p>我们一直用的<code>require
其实是Module
类的一个实例方法,内容很简单,先做一些参数检查,然后调用Module._load
方法,源码看这里:https://github.com/nodejs/node/blob/c6b96895cc74bc6bd658b4c6d5ea152d6e686d20/lib/internal/modules/cjs/loader.js#L970。精简版的代码如下:
MyModule.prototype.require = function (id) { return Module._load(id); }
MyModule._load
MyModule._load
是一个静态方法,这才是require
方法的真正主体,他干的事情其实是:
- 先检查请求的模块在缓存中是否已经存在了,如果存在了直接返回缓存模块的
exports
。- 如果不在缓存中,就
new
一个Module
实例,用这个实例加载对应的模块,并返回模块的exports
。
我们自己来实现下这两个需求,缓存直接放在Module._cache
这个静态变量上,这个变量官方初始化使用的是Object.create(null)
,这样可以使创建出来的原型指向null
,我们也这样做吧:
MyModule._cache = Object.create(null); MyModule._load = function (request) { // request是我们传入的路劲参数 const filename = MyModule._resolveFilename(request); // 先检查缓存,如果缓存存在且已经加载,直接返回缓存 const cachedModule = MyModule._cache[filename]; if (cachedModule !== undefined) { return cachedModule.exports; } // 如果缓存不存在,我们就加载这个模块 // 加载前先new一个MyModule实例,然后调用实例方法load来加载 // 加载完成直接返回module.exports const module = new MyModule(filename); // load之前就将这个模块缓存下来,这样如果有循环引用就会拿到这个缓存,但是这个缓存里面的exports可能还没有或者不完整 MyModule._cache[filename] = module; module.load(filename); return module.exports; }
上述代码对应的源码看这里:https://github.com/nodejs/node/blob/c6b96895cc74bc6bd658b4c6d5ea152d6e686d20/lib/internal/modules/cjs/loader.js#L735
可以看到上述源码还调用了两个方法:MyModule._resolveFilename
和MyModule.prototype.load
,下面我们来实现下这两个方法。
MyModule._resolveFilename
MyModule._resolveFilename
从名字就可以看出来,这个方法是通过用户传入的require
参数来解析到真正的文件地址的,源码中这个方法比较复杂,因为按照前面讲的,他要支持多种参数:内置模块,相对路径,绝对路径,文件夹和第三方模块等等,如果是文件夹或者第三方模块还要解析里面的package.json
和index.js
。我们这里主要讲原理,所以我们就只实现通过相对路径和绝对路径来查找文件,并支持自动添加js
和json
两种后缀名:
MyModule._resolveFilename = function (request) { const filename = path.resolve(request); // 获取传入参数对应的绝对路径 const extname = path.extname(request); // 获取文件后缀名 // 如果没有文件后缀名,尝试添加.js和.json if (!extname) { const exts = Object.keys(MyModule._extensions); for (let i = 0; i < exts.length; i++) { const currentPath = `${filename}${exts[i]}`; // 如果拼接后的文件存在,返回拼接的路径 if (fs.existsSync(currentPath)) { return currentPath; } } } return filename; }
上述源码中我们还用到了一个静态变量MyModule._extensions
,这个变量是用来存各种文件对应的处理方法的,我们后面会实现他。
MyModule._resolveFilename
对应的源码看这里:https://github.com/nodejs/node/blob/c6b96895cc74bc6bd658b4c6d5ea152d6e686d20/lib/internal/modules/cjs/loader.js#L822
MyModule.prototype.load
MyModule.prototype.load
是一个实例方法,这个方法就是真正用来加载模块的方法,这其实也是不同类型文件加载的一个入口,不同类型的文件会对应MyModule._extensions
里面的一个方法:
MyModule.prototype.load = function (filename) { // 获取文件后缀名 const extname = path.extname(filename); // 调用后缀名对应的处理函数来处理 MyModule._extensions[extname](this, filename); this.loaded = true; }
注意这段代码里面的this
指向的是module
实例,因为他是一个实例方法。对应的源码看这里: https://github.com/nodejs/node/blob/c6b96895cc74bc6bd658b4c6d5ea152d6e686d20/lib/internal/modules/cjs/loader.js#L942
加载js文件: MyModule._extensions['.js']
前面我们说过不同文件类型的处理方法都挂载在MyModule._extensions
上面的,我们先来实现.js
类型文件的加载:
MyModule._extensions['.js'] = function (module, filename) { const content = fs.readFileSync(filename, 'utf8'); module._compile(content, filename); }
可以看到js
的加载方法很简单,只是把文件内容读出来,然后调了另外一个实例方法_compile
来执行他。对应的源码看这里:https://github.com/nodejs/node/blob/c6b96895cc74bc6bd658b4c6d5ea152d6e686d20/lib/internal/modules/cjs/loader.js#L1098
编译执行js文件:MyModule.prototype._compile
MyModule.prototype._compile
是加载JS文件的核心所在,也是我们最常使用的方法,这个方法需要将目标文件拿出来执行一遍,执行之前需要将它整个代码包裹一层,以便注入exports, require, module, __dirname, __filename
,这也是我们能在JS文件里面直接使用这几个变量的原因。要实现这种注入也不难,假如我们require
的文件是一个简单的Hello World
,长这样:
module.exports = "hello world";
那我们怎么来给他注入module
这个变量呢?答案是执行的时候在他外面再加一层函数,使他变成这样:
function (module) { // 注入module变量,其实几个变量同理 module.exports = "hello world"; }
所以我们如果将文件内容作为一个字符串的话,为了让他能够变成上面这样,我们需要再给他拼接上开头和结尾,我们直接将开头和结尾放在一个数组里面:
MyModule.wrapper = [ '(function (exports, require, module, __filename, __dirname) { ', '\n});' ];
注意我们拼接的开头和结尾多了一个()
包裹,这样我们后面可以拿到这个匿名函数,在后面再加一个()
就可以传参数执行了。然后将需要执行的函数拼接到这个方法中间:
MyModule.wrap = function (script) { return MyModule.wrapper[0] + script + MyModule.wrapper[1]; };
这样通过MyModule.wrap
包装的代码就可以获取到exports, require, module, __filename, __dirname
这几个变量了。知道了这些就可以来写MyModule.prototype._compile
了:
MyModule.prototype._compile = function (content, filename) { const wrapper = Module.wrap(content); // 获取包装后函数体 // vm是nodejs的虚拟机沙盒模块,runInThisContext方法可以接受一个字符串并将它转化为一个函数 // 返回值就是转化后的函数,所以compiledWrapper是一个函数 const compiledWrapper = vm.runInThisContext(wrapper, { filename, lineOffset: 0, displayErrors: true, }); // 准备exports, require, module, __filename, __dirname这几个参数 // exports可以直接用module.exports,即this.exports // require官方源码中还包装了一层,其实最后调用的还是this.require // module不用说,就是this了 // __filename直接用传进来的filename参数了 // __dirname需要通过filename获取下 const dirname = path.dirname(filename); compiledWrapper.call(this.exports, this.exports, this.require, this, filename, dirname); }
上述代码要注意我们注入进去的几个参数和通过call
传进去的this
:
- this:
compiledWrapper
是通过call
调用的,第一个参数就是里面的this
,这里我们传入的是this.exports
,也就是module.exports
,也就是说我们js
文件里面this
是对module.exports
的一个引用。- exports:
compiledWrapper
正式接收的第一个参数是exports
,我们传的也是this.exports
,所以js
文件里面的exports
也是对module.exports
的一个引用。- require: 这个方法我们传的是
this.require
,其实就是MyModule.prototype.require
,也就是MyModule._load
。- module: 我们传入的是
this
,也就是当前模块的实例。- __filename:文件所在的绝对路径。
- __dirname: 文件所在文件夹的绝对路径。
到这里,我们的JS文件其实已经记载完了,对应的源码看这里:https://github.com/nodejs/node/blob/c6b96895cc74bc6bd658b4c6d5ea152d6e686d20/lib/internal/modules/cjs/loader.js#L1043
加载json文件: MyModule._extensions['.json']
加载json
文件就简单多了,只需要将文件读出来解析成json
就行了:
MyModule._extensions['.json'] = function (module, filename) { const content = fs.readFileSync(filename, 'utf8'); module.exports = JSONParse(content); }
exports
和module.exports
的区别
网上经常有人问,node.js
里面的exports
和module.exports
到底有什么区别,其实前面我们的手写代码已经给出答案了,我们这里再就这个问题详细讲解下。exports
和module.exports
这两个变量都是通过下面这行代码注入的。
compiledWrapper.call(this.exports, this.exports, this.require, this, filename, dirname);
初始状态下,exports === module.exports === {}
,exports
是module.exports
的一个引用,如果你一直是这样使用的:
exports.a = 1; module.exports.b = 2; console.log(exports === module.exports); // true
上述代码中,exports
和module.exports
都是指向同一个对象{}
,你往这个对象上添加属性并没有改变这个对象本身的引用地址,所以exports === module.exports
一直成立。
但是如果你哪天这样使用了:
exports = { a: 1 }
或者这样使用了:
module.exports = { b: 2 }
那其实你是给exports
或者module.exports
重新赋值了,改变了他们的引用地址,那这两个属性的连接就断开了,他们就不再相等了。需要注意的是,你对module.exports
的重新赋值会作为模块的导出内容,但是你对exports
的重新赋值并不能改变模块导出内容,只是改变了exports
这个变量而已,因为模块始终是module
,导出内容是module.exports
。
循环引用
Node.js对于循环引用是进行了处理的,下面是官方例子:
a.js
:
console.log('a 开始'); exports.done = false; const b = require('./b.js'); console.log('在 a 中,b.done = %j', b.done); exports.done = true; console.log('a 结束');
b.js
:
console.log('b 开始'); exports.done = false; const a = require('./a.js'); console.log('在 b 中,a.done = %j', a.done); exports.done = true; console.log('b 结束');
main.js
:
console.log('main 开始'); const a = require('./a.js'); const b = require('./b.js'); console.log('在 main 中,a.done=%j,b.done=%j', a.done, b.done);
当 main.js
加载 a.js
时, a.js
又加载 b.js
。 此时, b.js
会尝试去加载 a.js
。 为了防止无限的循环,会返回一个 a.js
的 exports
对象的 未完成的副本 给 b.js
模块。 然后 b.js
完成加载,并将 exports
对象提供给 a.js
模块。
那么这个效果是怎么实现的呢?答案就在我们的MyModule._load
源码里面,注意这两行代码的顺序:
MyModule._cache[filename] = module; module.load(filename);
上述代码中我们是先将缓存设置了,然后再执行的真正的load
,顺着这个思路我能来理一下这里的加载流程:
main
加载a
,a
在真正加载前先去缓存中占一个位置a
在正式加载时加载了b
b
又去加载了a
,这时候缓存中已经有a
了,所以直接返回a.exports
,即使这时候的exports
是不完整的。
总结
-
require
不是黑魔法,整个Node.js的模块加载机制都是JS
实现的。 - 每个模块里面的
exports, require, module, __filename, __dirname
五个参数都不是全局变量,而是模块加载的时候注入的。 - 为了注入这几个变量,我们需要将用户的代码用一个函数包裹起来,拼一个字符串然后调用沙盒模块
vm
来实现。 - 初始状态下,模块里面的
this, exports, module.exports
都指向同一个对象,如果你对他们重新赋值,这种连接就断了。 - 对
module.exports
的重新赋值会作为模块的导出内容,但是你对exports
的重新赋值并不能改变模块导出内容,只是改变了exports
这个变量而已,因为模块始终是module
,导出内容是module.exports
。 - 为了解决循环引用,模块在加载前就会被加入缓存,下次再加载会直接返回缓存,如果这时候模块还没加载完,你可能拿到未完成的
exports
。 - Node.js实现的这套加载机制叫CommonJS。
本文完整代码已上传GitHub:https://github.com/dennis-jiang/Front-End-Knowledges/blob/master/Examples/Node.js/Module/MyModule/index.js
参考资料
Node.js模块加载源码:https://github.com/nodejs/node/blob/c6b96895cc74bc6bd658b4c6d5ea152d6e686d20/lib/internal/modules/cjs/loader.js
Node.js模块官方文档:http://nodejs.cn/api/modules.html
文章的最后,感谢你花费宝贵的时间阅读本文,如果本文给了你一点点帮助或者启发,请不要吝啬你的赞和GitHub小星星,你的支持是作者持续创作的动力。
作者博文GitHub项目地址: https://github.com/dennis-jiang/Front-End-Knowledges
更多编程相关知识,可访问:编程教学!!
以上がNode.js のモジュール読み込みメカニズムの詳細な分析の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

Web開発におけるJavaScriptの主な用途には、クライアントの相互作用、フォーム検証、非同期通信が含まれます。 1)DOM操作による動的なコンテンツの更新とユーザーインタラクション。 2)ユーザーエクスペリエンスを改善するためにデータを提出する前に、クライアントの検証が実行されます。 3)サーバーとのリフレッシュレス通信は、AJAXテクノロジーを通じて達成されます。

JavaScriptエンジンが内部的にどのように機能するかを理解することは、開発者にとってより効率的なコードの作成とパフォーマンスのボトルネックと最適化戦略の理解に役立つためです。 1)エンジンのワークフローには、3つの段階が含まれます。解析、コンパイル、実行。 2)実行プロセス中、エンジンはインラインキャッシュや非表示クラスなどの動的最適化を実行します。 3)ベストプラクティスには、グローバル変数の避け、ループの最適化、constとletsの使用、閉鎖の過度の使用の回避が含まれます。

Pythonは、スムーズな学習曲線と簡潔な構文を備えた初心者により適しています。 JavaScriptは、急な学習曲線と柔軟な構文を備えたフロントエンド開発に適しています。 1。Python構文は直感的で、データサイエンスやバックエンド開発に適しています。 2。JavaScriptは柔軟で、フロントエンドおよびサーバー側のプログラミングで広く使用されています。

PythonとJavaScriptには、コミュニティ、ライブラリ、リソースの観点から、独自の利点と短所があります。 1)Pythonコミュニティはフレンドリーで初心者に適していますが、フロントエンドの開発リソースはJavaScriptほど豊富ではありません。 2)Pythonはデータサイエンスおよび機械学習ライブラリで強力ですが、JavaScriptはフロントエンド開発ライブラリとフレームワークで優れています。 3)どちらも豊富な学習リソースを持っていますが、Pythonは公式文書から始めるのに適していますが、JavaScriptはMDNWebDocsにより優れています。選択は、プロジェクトのニーズと個人的な関心に基づいている必要があります。

C/CからJavaScriptへのシフトには、動的なタイピング、ゴミ収集、非同期プログラミングへの適応が必要です。 1)C/Cは、手動メモリ管理を必要とする静的に型付けられた言語であり、JavaScriptは動的に型付けされ、ごみ収集が自動的に処理されます。 2)C/Cはマシンコードにコンパイルする必要がありますが、JavaScriptは解釈言語です。 3)JavaScriptは、閉鎖、プロトタイプチェーン、約束などの概念を導入します。これにより、柔軟性と非同期プログラミング機能が向上します。

さまざまなJavaScriptエンジンは、各エンジンの実装原則と最適化戦略が異なるため、JavaScriptコードを解析および実行するときに異なる効果をもたらします。 1。語彙分析:ソースコードを語彙ユニットに変換します。 2。文法分析:抽象的な構文ツリーを生成します。 3。最適化とコンパイル:JITコンパイラを介してマシンコードを生成します。 4。実行:マシンコードを実行します。 V8エンジンはインスタントコンピレーションと非表示クラスを通じて最適化され、Spidermonkeyはタイプ推論システムを使用して、同じコードで異なるパフォーマンスパフォーマンスをもたらします。

現実世界におけるJavaScriptのアプリケーションには、サーバー側のプログラミング、モバイルアプリケーション開発、モノのインターネット制御が含まれます。 2。モバイルアプリケーションの開発は、ReactNativeを通じて実行され、クロスプラットフォームの展開をサポートします。 3.ハードウェアの相互作用に適したJohnny-Fiveライブラリを介したIoTデバイス制御に使用されます。

私はあなたの日常的な技術ツールを使用して機能的なマルチテナントSaaSアプリケーション(EDTECHアプリ)を作成しましたが、あなたは同じことをすることができます。 まず、マルチテナントSaaSアプリケーションとは何ですか? マルチテナントSaaSアプリケーションを使用すると、Singの複数の顧客にサービスを提供できます


ホットAIツール

Undresser.AI Undress
リアルなヌード写真を作成する AI 搭載アプリ

AI Clothes Remover
写真から衣服を削除するオンライン AI ツール。

Undress AI Tool
脱衣画像を無料で

Clothoff.io
AI衣類リムーバー

AI Hentai Generator
AIヘンタイを無料で生成します。

人気の記事

ホットツール

ドリームウィーバー CS6
ビジュアル Web 開発ツール

AtomエディタMac版ダウンロード
最も人気のあるオープンソースエディター

ゼンドスタジオ 13.0.1
強力な PHP 統合開発環境

SublimeText3 Mac版
神レベルのコード編集ソフト(SublimeText3)

DVWA
Damn Vulnerable Web App (DVWA) は、非常に脆弱な PHP/MySQL Web アプリケーションです。その主な目的は、セキュリティ専門家が法的環境でスキルとツールをテストするのに役立ち、Web 開発者が Web アプリケーションを保護するプロセスをより深く理解できるようにし、教師/生徒が教室環境で Web アプリケーションを教え/学習できるようにすることです。安全。 DVWA の目標は、シンプルでわかりやすいインターフェイスを通じて、さまざまな難易度で最も一般的な Web 脆弱性のいくつかを実践することです。このソフトウェアは、
