Написание библиотеки, какая структура?
Я пишу библиотеку. Прямо сейчас, у меня есть все, написанное в одном файле .js, который выглядит как:
function doThis() {}
var thisVar = 5;
Я не был уверен, правильно ли это, поэтому я подумал:
function Lib() {
this.doThis = function() {}
this.thisVar = 5;
}
var lib = new Lib();
Затем, в основных файлах программы, мне нужно было бы вызвать все с помощью "lib", например "lib.thisVar" или "lib.doThis();".
Любые идеи, по которым было бы лучше, или они оба приемлемы? Заранее благодарю вас.
Ответы
Ответ 1
Оба приемлемы теоретически. Но оба риска могут называть конфликты с другими частями/библиотеками, используемыми в вашем приложении.
В первом случае вы рискуете столкнуться с именами коллизий для отдельных функций, тогда как в последнем случае вы рискуете назвать именования коллизий для функции, которую вы выбираете для обертки библиотеки (Lib
).
Предпочтительным методом было бы обернуть их в отдельное пространство имен, как показано на этой странице:
http://frugalcoder.us/post/2010/02/11/js-classes.aspx
Ответ 2
Чтобы избежать загромождения глобального пространства имен, я использую такую структуру:
var MyLib = {
vars: {
var1: 'value1',
var2: 'value2'
},
func1: function () {
return this.vars.var1;
},
func2: function () {
alert("This is func2");
}
};
MyLib.func1();
MyLib.func2();
Вы заметите, что я поместил все переменные в свой собственный под-объект, это делается исключительно для удобства чтения и разработки.
РЕДАКТИРОВАТЬ 1:
Вот еще один метод, который я использую
var MyLib = (function MyLib() {
var _privateVars = {
"someVar": "This value made public by `someMethod`",
"privateVar": "Can't see this value"
};
// Return the constructor
return function MyLibConstructor() {
var _this = this; // Cache the `this` keyword
_this.someMethod = function () {
// Access a private variable
return _privateVars.someVar;
};
_this.someOtherMethod = function () {
// Some other functionality
};
};
}());
var myLib = new MyLib(); // invoke
console.log( myLib.someMethod() );
В этой структуре используется JS Closures и функция конструктора, поэтому ее легко сохранить частные переменные private.
ИЗМЕНИТЬ 2:
Кроме того, я также использовал другую настройку закрытия, которая не возвращает конструктор (например, var x = new MyLib();
).
(function(window) {
var _private = {},
methods = {},
topic, init;
methods.set = function(value) {
// Set the property & value
_private[topic] = value;
return this;
};
// A simple get method
methods.get = function(callback) {
var response = null;
// Return the value of topic (property) in a callback
if (!!callback && typeof callback === 'function') {
if (_private.hasOwnProperty(topic)) {
response = _private[topic];
}
callback.call(this, response);
}
return this;
};
// Init method setting the topic and returning the methods.
init = function(_topic) {
topic = _topic;
return methods;
};
// Exposure when being used in an AMD environment, e.g. RequireJS
if (typeof define === 'function' && define.amd) {
define(function() {
return init;
});
return;
}
// Exposure when being used with NodeJS
if ('undefined' !== typeof module && module.exports) {
module.exports = init;
return;
}
// Last-in-the-line exposure to the window, if it exists
window.myLib = init;
// This line either passes the `window` as an argument or
// an empty Object-literal if `window` is not defined.
}(('undefined' !== typeof window) ? window : {}));
И чтобы увидеть это в действии:
myLib('something').set('made public, outside of the closure by the `get` method');
myLib('something').get(function(a){
console.log(a);
});
Также обратите внимание на то, как я показываю myLib
, принимая во внимание, где он выполняется, и как он включается.
РЕДАКТИРОВАТЬ 3 (7/2017):
В качестве полного стека (w/ Node.js) JavaScript-инженера и появления Browserify я полностью рекомендую использовать шаблон стиля module
в стиле Nodejs, используя либо Gulp, либо Grunt как систему сборки для компиляции нескольких файлов (развязанных, меньших бит кода) в одну библиотеку.
Эта модель помогает стимулировать более функциональный подход, позволяя разработчикам абстрагироваться от более общих функций внутри библиотеки в отдельные файлы, что значительно облегчает процесс разработки.
О, и используйте ES6!
// file: src/divideIntByFour.js
const divideIntByFour = (int) => {
return int / 4;
};
module.exports = divideIntByFour;
... В качестве обобщенного примера
Ответ 3
Посмотрите на шаблон модуля JavaScript.
http://www.adequatelygood.com/2010/3/JavaScript-Module-Pattern-In-Depth
Вы можете подумать о том, чтобы сделать вашу библиотеку совместимой с require.js, которая является основой для такого рода вещей.
http://requirejs.org
Ответ 4
Да, последний подход лучше, поскольку он не создает много глобальных переменных ( "глобальное загрязнение границ" ), а namespaces их на объекте.
Тем не менее, нет необходимости в конструкторе (Lib
), вы должны создать экземпляр Lib
только один раз (после шаблона singleton); и вам не нужен прототип. Вместо этого используйте простой литерал объекта:
var lib = {
doThis: function() {
},
thisVar: 5
};
Для частных (хотя и статических) переменных и лучшей организации кода также рассмотрите шаблон модуля (или здесь):
var lib = (function(){
var thisVar = 5;
function doThis() {}
return { // "export"
doThat: doThis
};
})();
Ответ 5
Последнее предпочтительнее. В частности, при написании библиотек важно избегать загрязнения глобального пространства имен как можно больше. Ваш код должен как можно меньше вмешиваться в существующий код.
Ответ 6
Сообщение старое, но, может быть, мои 2 цента могут помочь. Я видел и делал это с несколькими библиотеками:
nameOfLibrary = function() { //name of library needs to be unique enough not to conflict with anyones code
nameOfLibrary = {};
//you can declare variables or functions only visible to this scope anywhere here
var randomVar = 'testVar';
function onlyVisibleHere() { /* doSomething */ }
//if you want functions visible to outside set them like so
nameOfLibrary.nameOfFunction = function() {
//code to run
}
//lastly return your library
return nameOfLibrary;
}
Ответ 7
UMD
обычно используется.
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
typeof define === 'function' && define.amd ? define(factory) :
(global.MyLibrary = factory());
}(this, (function () { 'use strict';
// Private implementation here
let secret = "message";
// Whatever is returned is what `global.MyLibrary` will be
return {
getSecret: () => secret
};
})));
console.log(MyLibrary.getSecret()); // "message"
console.log(secret); // Uncaught ReferenceError: secret is not defined
Это IIFE, который немедленно вызывается с двумя аргументами: this
и выражение функции, которые являются параметрами global
и factory
соответственно. Это позволяет ограничить объем вашей реализации и предотвращает конфликты имен.
Если script запущен в браузере, this
будет window
, поэтому window.MyLibrary будет равно factory()
, который в конечном итоге возвращается из выражения функции во втором аргументе. Это API-интерфейс библиотеки.
Ответ 8
Второй подход - рекомендуемая практика, но это можно улучшить, создав новое пространство имен. Преимущество заключается в том, что вы сможете избежать всех коллизий имен, которые могут возникнуть, плюс вы получите лучший контроль над своим кодом.
Я создал пространство имен, называемое "MyNamespace", и я храню в нем свой класс библиотеки. Таким образом, даже если есть другой файл с именем класса "Библиотека", конфликт имен не будет.
(function($){
$.Library = function(){
this.memory = {};
this.doSomething = function() {};
};
$.Library.prototype = { //these will be common for all the objects
commonFunction : function() {
},
function2 : function() {
}
};
})(MyNameSpace);
Используя приведенный ниже код, вы можете создавать новые объекты для класса Library.
<script>
var lib = new MyNamespace.Library;
</script>