Эта статья была впервые опубликована в публичном аккаунте vivo Internet Technology WeChat.
Связь: https://mp.weixin.qq.com/s/15sedEuUVTsgyUm1lswrKA
Добавить Автора
Введение
Предыдущий"Научная серия о внешнем интерфейсе (2): Node.js смотрит на мир под другим углом", мы говорили о вещах, связанных с Node.js. Node.js может превратиться в такой бардак после своего рождения. Он неотделим от своей зрелой модульной реализации. Модульность Node.js реализована на основе спецификации CommonJS. Так что же такое CommonJS?
Давайте посмотрим, этоВикипедияОпределение на:
CommonJS — это проект, целью которого является создание соглашений о модулях для JavaScript за пределами веб-браузера. Основной причиной создания этого проекта было отсутствие общепринятой формы модуля модуля для JavaScript-скриптов, которые можно было бы повторно использовать в средах, отличных от тех, что предоставляются обычными веб-браузерами, выполняющими скрипты JavaScript.
Мы знаем, что долгое время, до появления Node.js, в языке JavaScript не было концепции модульности.После переноса языка JavaScript на сервер перед лицом сложных бизнес-сценариев, таких как файловая система, сеть, операционная система и т. д., модульность становится незаменимой.. Поэтому спецификации Node.js и CommonJS дополняют друг друга и дополняют друг друга, и в глаза разработчикам они попали вместе.
Видно, что CommonJS изначально обслуживал серверную часть, поэтому я сказал, что CommonJS — это не фронтенд, а его носитель — язык фронтенда JavaScript, который оказал глубокое влияние на преобладание модульности фронтенда и заложил прочный фундамент. CommonJS: Не внешний интерфейс, но революционный внешний интерфейс!
2. Зачем нужна модульность?
1. Как выглядит интерфейс без модульности?
Перед «Web: путь вперед», лета, «мы упомянули JavaScript Начало рождения только в качестве языка сценариев, выполняйте некоторую простую проверку формы и т. Д. Следовательно, небольшое количество кода, записано непосредственно к началу
// index.html
<script>
var name = 'morrain'
var age = 18
</script>
С дальнейшим усложнением бизнеса, после рождения Ajax, фронтенд может делать все больше и больше вещей, а количество кода быстро растет, разработчики начинают писать JavaScript в отдельный js файл, который отделен от html файл. Как следующее:// index.html
<script src="./mine.js"></script>
// mine.js
var name = 'morrain'
var age = 18
Позже подключились другие разработчики, и было представлено больше js-файлов:// index.html
<script src="./mine.js"></script>
<script src="./a.js"></script>
<script src="./b.js"></script>
// mine.js
var name = 'morrain'
var age = 18
// a.js
var name = 'lilei'
var age = 15
// b.js
var name = 'hanmeimei'
var age = 13
Нетрудно обнаружить, что проблема пришла! До ES6 в JavaScript не было модульной системы и концепции закрытой области видимости, поэтому переменные, объявленные в трех вышеперечисленных js-файлах, будут существовать в глобальной области видимости. Разные разработчики поддерживают разные js-файлы, и трудно гарантировать, что они не конфликтуют с другими js-файлами. Загрязнение глобальных переменных стало кошмаром для разработчиков.2. Модульные прототипы
Чтобы решить проблему загрязнения глобальных переменных, разработчики стали использовать метод пространства имен, так как именование будет конфликтовать, добавьте пространство имен, как показано ниже:
// index.html
<script src="./mine.js"></script>
<script src="./a.js"></script>
<script src="./b.js"></script>
// mine.js
app.mine = {}
app.mine.name = 'morrain'
app.mine.age = 18
// a.js
app.moduleA = {}
app.moduleA.name = 'lilei'
app.moduleA.age = 15
// b.js
app.moduleB = {}
app.moduleB.name = 'hanmeimei'
app.moduleB.age = 13
На данный момент уже существует расплывчатое понятие модульности, просто реализованное с помощью пространств имен. Это в какой-то степени решает проблему конфликтов имен, разработчики b.js-модулей могут легко пройти мимоapp.moduleA.nameчтобы получить имя в модуле А, но и черезapp.moduleA.name= 'rename' для произвольного изменения имени в модуле А, но модуль А об этом не знает! Это явно не разрешено.Умные разработчики начали использовать преимущества области действия языка JavaScript и использовать характеристики замыканий для решения вышеуказанной проблемы.
// index.html
<script src="./mine.js"></script>
<script src="./a.js"></script>
<script src="./b.js"></script>
// mine.js
app.mine = (function(){
var name = 'morrain'
var age = 18
return {
getName: function(){
return name
}
}
})()
// a.js
app.moduleA = (function(){
var name = 'lilei'
var age = 15
return {
getName: function(){
return name
}
}
})()
// b.js
app.moduleB = (function(){
var name = 'hanmeimei'
var age = 13
return {
getName: function(){
return name
}
}
})()
Теперь модули b.js доступны черезapp.moduleA.getName() для получения имени модуля A, но имена каждого модуля хранятся в соответствующих функциях, и другие модули не могут их изменить. В таком дизайне уже есть тень модульности, каждый модуль хранит в себе приватные вещи и открывает интерфейсы для использования другими модулями, но он все равно не элегантен и не совершенен. Например, в приведенном выше примере модуль B может получить вещи модуля A, но модуль A не может получить вещи модуля B, потому что три вышеуказанных модуля загружаются по порядку и зависят друг от друга. Когда бизнес-масштаб клиентского приложения достаточно велик, поддерживать эту зависимость становится чрезвычайно сложно.
Подводя итог, интерфейс должен быть модульным, а модуляризация должна не только решать проблемы загрязнения глобальных переменных и защиты данных, но и решать проблемы поддержания зависимостей между модулями.
3. Введение в спецификацию CommonJS
Поскольку для решения вышеуказанных проблем JavaScript должен быть модульным, ему необходимо сформулировать модульную спецификацию. CommonJS — это модульная спецификация для решения вышеуказанных проблем. Спецификация — это спецификация. Давайте посмотрим вместе.
1. Обзор CommonJS
Приложение Node.js состоит из модулей,Каждый файл является модулем и имеет свою собственную область видимости. Переменные, функции и классы, определенные в файле, являются частными и невидимыми для других файлов.
// a.js
var name = 'morrain'
var age = 18
В приведенном выше коде a.js — это модуль в приложении Node.js, и объявленные в нем переменные name и age являются частными для a.js, и доступ к другим файлам недоступен.Спецификация CommonJS также предусматривает, что внутри каждого модуля можно использовать две переменные: require и module.
require используется для загрузки модуля
module представляет текущий модуль, который представляет собой объект, сохраняющий информацию о текущем модуле. exports — это атрибут модуля, который сохраняет интерфейс или переменную для экспорта текущим модулем Значение, полученное модулем, загруженным с помощью require, — это значение, экспортируемое этим модулем с помощью exports
// a.js
var name = 'morrain'
var age = 18
module.exports.name = name
module.exports.getAge = function(){
return age
}
//b.js
var a = require('a.js')
console.log(a.name) // 'morrain'
console.log(a.getAge())// 18
2. Экспорт CommonJS
Для удобства, когда Node.js реализует спецификацию CommonJS, он предоставляет каждому модулю приватную переменную экспорта, указывающую на module.exports. Вы можете понять, что Node.js добавляет следующую строку кода в начале каждого модуля.
var exports = module.exports
Таким образом, приведенный выше код также можно записать так:// a.js
var name = 'morrain'
var age = 18
exports.name = name
exports.getAge = function(){
return age
}
На что следует обратить особое внимание, так это на то, что exports — это частная локальная переменная в модуле, она просто указывает на module.exports, поэтому недопустимо присваивать значение непосредственно экспорту, из-за чего экспорт больше не указывает на module.exports. .
Следующим образом:
// a.js
var name = 'morrain'
var age = 18
exports = name
Если внешний интерфейс модуля представляет собой одно значение, его можно экспортировать с помощью module.exports.
// a.js
var name = 'morrain'
var age = 18
module.exports = name
3. Требование CommonJS
Основная функция команды require — прочитать и выполнить файл js, а затем вернуть объект экспорта модуля. Если указанный модуль не найден, будет сообщено об ошибке.
При первой загрузке модуля Node.js кэширует модуль. Когда модуль загружается позже, свойство module.exports модуля напрямую извлекается из кеша и возвращается.
// a.js
var name = 'morrain'
var age = 18
exports.name = name
exports.getAge = function(){
return age
}
// b.js
var a = require('a.js')
console.log(a.name) // 'morrain'
a.name = 'rename'
var b = require('a.js')
console.log(b.name) // 'rename'
Как показано выше, когда модуль A требуется во второй раз, модуль A не перезагружается и не выполняется. Вместо этого он напрямую возвращает результат первого запроса, то есть module.exports модуля A.
Также обратите внимание, что механизм загрузки модулей CommonJS заключается в том, что требуется копия экспортируемого значения. То есть после экспорта значения изменения внутри модуля не могут повлиять на это значение.
// a.js
var name = 'morrain'
var age = 18
exports.name = name
exports.age = age
exports.setAge = function(a){
age = a
}
// b.js
var a = require('a.js')
console.log(a.age) // 18
a.setAge(19)
console.log(a.age) // 18
В-четвертых, реализация CommonJS
了解 CommonJS 的规范后,不难发现我们在写符合 CommonJS 规范的模块时,无外乎就是使用了 require 、 exports 、 module 三个东西,然后一个 js 文件就是一个模块。 Следующим образом:
// a.js
var name = 'morrain'
var age = 18
exports.name = name
exports.getAge = function () {
return age
}
// b.js
var a = require('a.js')
console.log('a.name=', a.name)
console.log('a.age=', a.getAge())
var name = 'lilei'
var age = 15
exports.name = name
exports.getAge = function () {
return age
}
// index.js
var b = require('b.js')
console.log('b.name=',b.name)
Если мы предоставим функцию немедленного выполнения require , exports , module Три параметра, код модуля будет помещен в эту функцию немедленного выполнения. Значение экспорта модуля помещается в module.exports, который реализует загрузку модуля. Следующим образом:
(function(module, exports, require) {
// b.js
var a = require("a.js")
console.log('a.name=', a.name)
console.log('a.age=', a.getAge())
var name = 'lilei'
var age = 15
exports.name = name
exports.getAge = function () {
return age
}
})(module, module.exports, require)
Зная этот принцип, легко преобразовать код проекта, соответствующий спецификации модуля CommonJS, в код, поддерживаемый браузером. Многие инструменты реализованы таким образом, начиная с модуля ввода, помещая все зависимые модули в свои собственные функции и упаковывая все модули в файл js, который может работать в браузере. Например, Browserify, webpack и т. д.
Давайте возьмем webpack в качестве примера, чтобы увидеть, как реализовать поддержку спецификации CommonJS. Когда мы используем webpack для сборки, мы упаковываем содержимое файла каждого модуля в файл js в следующем формате: поскольку это анонимная функция, которая выполняется немедленно, ее можно запустить прямо в браузере.
// bundle.js
(function (modules) {
// 模块管理的实现
})({
'a.js': function (module, exports, require) {
// a.js 文件内容
},
'b.js': function (module, exports, require) {
// b.js 文件内容
},
'index.js': function (module, exports, require) {
// index.js 文件内容
}
})
Далее нам нужно реализовать содержимое управления модулями в соответствии со спецификацией CommonJS. Во-первых, мы знаем, что в спецификации CommonJS указано, что загруженные модули будут кэшироваться, поэтому для кэширования загруженных модулей необходим объект, а затем для загрузки модуля необходима функция require, при загрузке модуль должен быть сгенерирован. , и модуль должен быть загружен.Существует свойство экспорта, чтобы получить то, что экспортирует модуль.
// bundle.js
(function (modules) {
// 模块管理的实现
var installedModules = {}
/**
* 加载模块的业务逻辑实现
* @param {String} moduleName 要加载的模块名
*/
var require = function (moduleName) {
// 如果已经加载过,就直接返回
if (installedModules[moduleName]) return installedModules[moduleName].exports
// 如果没有加载,就生成一个 module,并放到 installedModules
var module = installedModules[moduleName] = {
moduleName: moduleName,
exports: {}
}
// 执行要加载的模块
modules[moduleName].call(modules.exports, module, module.exports, require)
return module.exports
}
return require('index.js')
})({
'a.js': function (module, exports, require) {
// a.js 文件内容
},
'b.js': function (module, exports, require) {
// b.js 文件内容
},
'index.js': function (module, exports, require) {
// index.js 文件内容
}
})
Можно видеть, что основная спецификация CommonJS выполняется в приведенной выше реализации. Это очень просто, не так сложно, как вы думаете.
5. Другие фронтальные модульные решения
Мы очень хорошо знакомы со спецификацией CommonJS. Основная функция команды require состоит в том, чтобы прочитать и выполнить файл js, а затем вернуть объект экспорта модуля. Это возможно на стороне сервера, потому что сторона сервера загружает и выполняет Потреблением времени файла можно пренебречь.Загрузка модуля синхронно загружается во время выполнения.После выполнения команды require файл выполняется и значение, экспортируемое модулем, успешно получено.
Эта спецификация по своей сути не подходит для браузеров, поскольку она является синхронной. Вполне возможно, что каждый раз, когда браузер загружает файл, он должен отправлять сетевой запрос, чтобы получить его. Если скорость сети низкая, это займет очень много времени. подвешенное состояние.
Чтобы решить эту проблему, позже были разработаны многие модульные спецификации внешнего интерфейса, в том числе CommonJS, которые примерно выглядят следующим образом:
1. AMD (определение асинхронного модуля)
Прежде чем мы поговорим об AMD, ознакомьтесь с RequireJS.
Официальный сайт описывает это следующим образом:
"RequireJS is a JavaScript file and module loader. It is optimized for in-browser use, but it can be used in other JavaScript environments, like Rhino and Node. Using a modular script loader like RequireJS will improve the speed and quality of your code."
Перевод примерно такой:
RequireJS — это загрузчик файлов и модулей js. Он отлично подходит для использования в браузерах, но его также можно использовать в других средах js, таких как Rhino и Node. Загрузка модульных скриптов с помощью RequireJS повышает скорость и качество загрузки кода.
Это решает проблему, заключающуюся в том, что спецификацию CommonJS нельзя использовать на стороне браузера, а AMD представляет собой стандартизированный вывод определения модуля в процессе продвижения RequireJS.
Давайте посмотрим на реализацию спецификации AMD:
<script src="require.js"></script>
<script src="a.js"></script>
Прежде всего, нам нужно добавить в html-файл библиотеку инструментов require.js, которая предоставляет такие функции, как определение модулей и загрузка модулей. Он предоставляет глобальную функцию определения для определения модулей. Таким образом, после импорта файла require.js другие импортируемые файлы могут использовать определение для определения модуля.
define(id?, dependencies?, factory)
id: необязательный параметр, используемый для определения идентификатора модуля. Если этот параметр не указан, используется имя файла js (с удаленным расширением). Если в файле js определен только один модуль, этот параметр можно опустить. зависимости: необязательный параметр, представляющий собой массив, представляющий зависимости текущего модуля.Если зависимости нет, вы не можете передать factory:фабричный метод, функцию или объект, который будет выполняться при инициализации модуля. Если это функция, она должна быть выполнена только один раз, а возвращаемое значение — это значение, которое будет экспортировано модулем. Если объект, этот объект должен быть выходным значением модуля.
Таким образом, модуль A можно определить следующим образом:
// a.js
define(function(){
var name = 'morrain'
var age = 18
return {
name,
getAge: () => age
}
})
// b.js
define(['a.js'], function(a){
var name = 'lilei'
var age = 15
console.log(a.name) // 'morrain'
console.log(a.getAge()) // 18
return {
name,
getAge: () => age
}
})
Он загружает модуль асинхронно, и загрузка модуля не влияет на выполнение следующего за ним оператора. Все операторы, которые зависят от этого модуля, определены в функции обратного вызова, и функция обратного вызова не будет выполняться, пока загрузка не будет завершена.
Основная идея RequireJS — определить код как модуль через метод define. Когда этот модуль требуется, он начинает загружать модули, от которых он зависит, а когда все зависимые модули загружаются, он начинает выполнять функцию обратного вызова, а возвращаемое значение — это значение, экспортируемое модулем. AMD — это аббревиатура от «Определение асинхронного модуля», что означает «Определение асинхронного модуля».
2. CMD (Общее определение модуля)
Подобно AMD, CMD представляет собой стандартизированный вывод определения модуля Sea.js в процессе продвижения. Sea.js был написан Ю Бо из Али. Он родился после RequireJS, Юбо посчитал, что спецификация AMD асинхронна, а организация модулей неестественна и интуитивно понятна. Поэтому он ищет такую форму письма, как CommonJS. Итак, ЦМД.
Официальный сайт Sea.js представляет Sea.js следующим образом:
«Sea.js использует простой и естественный способ написания и организации кода со следующими основными функциями:»
«Простая и удобная спецификация определения модуля: Sea.js соответствует спецификации CMD и может писать код модуля, как Node.js. Естественная и интуитивно понятная организация кода: автоматическая загрузка зависимостей, краткая и четкая конфигурация, позволяющая нам получать больше удовольствия от кодирования. "
Давайте посмотрим на реализацию спецификации CMD:
<script src="sea.js"></script>
<script src="a.js"></script>
Прежде всего, нам нужно добавить в html-файл библиотеку инструментов sea.js, которая предоставляет такие функции, как определение модулей и загрузка модулей. Он предоставляет глобальную функцию определения для определения модулей. Таким образом, после импорта файла sea.js другие импортированные файлы могут использовать определение для определения модуля.
// 所有模块都通过 define 来定义
define(function(require, exports, module) {
// 通过 require 引入依赖
var a = require('xxx')
var b = require('yyy')
// 通过 exports 对外提供接口
exports.doSomething = ...
// 或者通过 module.exports 提供整个接口
module.exports = ...
})
// a.js
define(function(require, exports, module){
var name = 'morrain'
var age = 18
exports.name = name
exports.getAge = () => age
})
// b.js
define(function(require, exports, module){
var name = 'lilei'
var age = 15
var a = require('a.js')
console.log(a.name) // 'morrain'
console.log(a.getAge()) //18
exports.name = name
exports.getAge = () => age
})
Секрет того, что Sea.js может писать код модуля в синхронной форме, такой как CommonsJS, заключается в следующем: когда требуется модуль b.js, после загрузки b.js Sea.js сканирует код b.js, находит ключевое слово require, извлечь Все зависимости загружаются, и после загрузки всех зависимых модулей выполняется функция обратного вызова.В это время, когда выполняется строка require('a.js'), a.js загружается в память.
3. Модуль ES6
Вышеупомянутый CommonJS служит на стороне сервера, а AMD и CMD — на стороне браузера, но у всех них есть одна общая черта:Только после запуска кода можно определить экспортируемый контент.,Реализация CommonJSможно увидеть в .
Следует также отметить, что AMD и CMD — это схемы загрузки модулей, разработанные разработчиками в сообществе, а не стандарты на уровне языка.Начиная с ES6, на уровне языковых стандартов реализованы модульные функции, причем реализация достаточно проста, она может полностью заменить спецификации CommonJS, CMD и AMD и стать единым модульным решением для браузеров и серверов.
То же самое верно, еще в мае 2013 года Исаак З. Шлютер, автор NPM, менеджера пакетов для Node.js, сказал:CommonJS устарел, основные разработчики Node.js решили отказаться от спецификации. На это есть две основные причины. Одна из них заключается в том, что Node.js не полностью соответствует спецификации CommonJS.CommonJS экспортаАтрибут экспорта, упомянутый выше, добавлен самим Node.js, который в то время решил не следовать за развитием CommonJS. Во-вторых, Node.js также постепенно заменяет CommonJS модулем ES6.
2017.9.12 Node.js выпустил версию 8.5.0 для поддержки модуля ES6. Это только на экспериментальной стадии. Необходимо добавить параметр --experimental-modules.
2019.11.21 Параметр --experimental-modules был отменен в версии 13.2.0, выпущенной Node.js, что означает, что, начиная с версии v13.2, Node.js по умолчанию включил поддержку модуля ES6.
(1) Синтаксис модуля ES6
Две проблемы, которые необходимо учитывать при любой модуляризации, — это импорт зависимостей и экспорт интерфейсов. То же самое верно и для модуля ES6.Функция модуля в основном состоит из двух команд: экспорта и импорта. Команда экспорта используется для экспорта внешнего интерфейса модуля, а команда импорта используется для импорта содержимого, экспортируемого другими модулями.
Для конкретного синтаксиса, пожалуйста, обратитесь кУчебник учителя Жуань Ифэн, пример следующий:
// a.js
export const name = 'morrain'
const age = 18
export function getAge () {
return age
}
//等价于
const name = 'morrain'
const age = 18
function getAge (){
return age
}
export {
name,
getAge
}
После использования команды экспорта для определения внешнего интерфейса модуля другие файлы JavaScript могут загрузить модуль с помощью команды импорта.
// b.js
import { name as aName, getAge } from 'a.js'
export const name = 'lilei'
console.log(aName) // 'morrain'
const age = getAge()
console.log(age) // 18
// 等价于
import * as a from 'a.js'
export const name = 'lilei'
console.log(a.name) // 'morrin'
const age = a.getAge()
console.log(age) // 18
Помимо указания определенного выходного значения для загрузки, вы также можете использовать глобальную загрузку, то есть использовать звездочку (*) для указания объекта, на который загружаются все выходные значения.
Как видно из приведенного выше примера, при использовании команды импорта пользователю необходимо знать имя импортируемой переменной, что иногда вызывает затруднения, поэтому модуль ES6 обеспечивает удобное использование, используйте команду экспорта по умолчанию, чтобы указать вывод по умолчанию. для модуля.
// a.js
const name = 'morrain'
const age = 18
function getAge () {
return age
}
export default {
name,
getAge
}
// b.js
import a from 'a.js'
console.log(a.name) // 'morrin'
const age = a.getAge()
console.log(age) // 18
Очевидно, модуль может иметь только один вывод по умолчанию, поэтому команду экспорта по умолчанию можно использовать только один раз. В то же время вы можете видеть, что нет необходимости использовать фигурные скобки после команды импорта.
В дополнение к основному синтаксису есть также использование, составное написание экспорта и импорта, экспорт * из 'a', динамическая загрузка import() и т. д. Вы можете изучить это самостоятельно.
Упомянутый ранее Node.js уже поддерживает модуль ES6 по умолчанию, а браузер полностью поддерживает модуль ES6. Что касается node.js и браузера, как использовать модуль ES6, вы можете узнать сами.
(2) Разница между модулем ES6 и CommonJS
CommonJS может определить экспортируемый интерфейс только во время выполнения, а фактически экспортируется объект. Идея дизайна модуля ES6 состоит в том, чтобы быть как можно более статичным, чтобы зависимости модуля и импортированных и экспортируемых переменных можно было определить во время компиляции, что является так называемой «загрузкой во время компиляции».
Из-за этого команда импорта имеет эффект подъема и поднимается в голову всего модуля, который выполняется первым. Следующий код допустим, поскольку импорт выполняется до вызова getAge.
// a.js
export const name = 'morrain'
const age = 18
export function getAge () {
return age
}
// b.js
const age = getAge()
console.log(age) // 18
import { getAge } from 'a.js'
Кроме того, поскольку модули ES6 загружаются во время компиляции, нельзя использовать выражения и переменные, поскольку это синтаксические конструкции, которые могут получать результаты только во время выполнения. Следующим образом:
// 报错
import { 'n' + 'ame' } from 'a.js'
// 报错
let module = 'a.js'
import { name } from module
спередиТребование CommonjsКак уже упоминалось, require — это копия экспортируемого значения. То есть после экспорта значения изменения внутри модуля не могут повлиять на это значение. Давайте посмотрим, как выглядит модуль ES.
Давайте рассмотрим предыдущий пример:
// a.js
var name = 'morrain'
var age = 18
exports.name = name
exports.age = age
exports.setAge = function(a){
age = a
}
// b.js
var a = require('a.js')
console.log(a.age) // 18
a.setAge(19)
console.log(a.age) // 18
Используйте модуль ES6 для реализации этого примера:
// a.js
var name = 'morrain'
var age = 18
const setAge = a => age = a
export {
name,
age,
setAge
}
// b.js
import * as a from 'a.js'
console.log(a.age) // 18
a.setAge(19)
console.log(a.age) // 19
Модуль ES6 — это спецификация для модулей в ES6.ES6 — это аббревиатура ECMAScript 6.0, стандарта языка JavaScript следующего поколения, официально выпущенного в июне 2015 года. В первом разделе «Веб: все время вперед и до конца, чтобы забыть о реке» мы упоминали, что с момента разработки до выпуска ES6 прошло более десяти лет, и он представил множество новых функций и новых механизмов. стоимость обучения по-прежнему довольно велика.
Далее поговорим о ES6+ и Babel, следите за обновлениями…
6. Ссылки
Больше контента, пожалуйста, обратите вниманиеживые интернет-технологииПубличный аккаунт WeChat
Примечание. Статья перепечатана, пожалуйста, свяжитесь с микросигналом:Labs2020соединять.