Після багатьох редагувань ця відповідь стала чудовиськом у довжину. Прошу вибачення заздалегідь.
По-перше, eval()
це не завжди погано, і може принести користь у продуктивності, наприклад, якщо використовується при ледачій оцінці. Lazy-оцінка схожа на lazy-load, але ви по суті зберігаєте свій код у рядках, а потім використовуєте eval
або new Function
оцінюєте код. Якщо ви скористаєтеся деякими хитрощами, то це стане набагато кориснішим від зла, але якщо цього не зробити, це може призвести до поганих речей. Ви можете подивитися в моїй модульній системі, яка використовує цю схему: https://github.com/TheHydroImpulse/resolve.js . Resolve.js використовує eval, а не new Function
головним чином для моделювання CommonJS exports
та module
змінних, доступних у кожному модулі, та new Function
обертає ваш код в анонімній функції, хоча я завершую обгортання кожного модуля у функції, я роблю це вручну в поєднанні з eval.
Більше про це ви читаєте в наступних двох статтях, пізніші також посилаються на першу.
Генератори гармонії
Тепер, коли генератори нарешті приземлилися у V8 і, таким чином, у Node.js, під прапором ( --harmony
або --harmony-generators
). Це значно зменшує кількість пекельних зворотних дзвінків. Це робить написання асинхронного коду справді чудовим.
Найкращим способом використання генераторів є використання якоїсь бібліотеки контрольних потоків. Це дасть можливість потоку продовжувати йти, коли ви поступатиметесь в межах генераторів.
Резюме / огляд:
Якщо ви не знайомі з генераторами, вони практикують призупиняти виконання спеціальних функцій (званих генераторами). Така практика називається прибутковістю за допомогою yield
ключового слова.
Приклад:
function* someGenerator() {
yield []; // Pause the function and pass an empty array.
}
Таким чином, кожного разу, коли ви вперше викликаєте цю функцію, вона поверне новий екземпляр генератора. Це дозволяє зателефонувати next()
на цей об’єкт, щоб запустити або відновити генератор.
var gen = someGenerator();
gen.next(); // { value: Array[0], done: false }
Ви б продовжували телефонувати next
до done
повернення true
. Це означає, що генератор повністю закінчив його виконання, і yield
заяв більше немає .
Контрольний потік:
Як бачите, керування генераторами не є автоматичним. Кожне потрібно продовжувати вручну. Ось чому використовуються бібліотеки управління потоками типу co .
Приклад:
var co = require('co');
co(function*() {
yield query();
yield query2();
yield query3();
render();
});
Це дає можливість записувати все в Node (і браузер із Facebook-регенератором, який приймає в якості вихідного коду вихідний код, що використовує генератори гармонії та розбиває повністю сумісний код ES5) із синхронним стилем.
Генератори все ще досить нові, і тому потрібен Node.js> = v11.2. Коли я це пишу, v0.11.x все ще нестабільний, тому багато рідних модулів зламані і будуть до v0.12, де нативний API заспокоїться.
Щоб додати до моєї оригінальної відповіді:
Нещодавно я віддав перевагу більш функціональному API в JavaScript. Конвенція використовує OOP за лаштунками, коли це необхідно, але це спрощує все.
Візьмемо для прикладу систему перегляду (клієнт або сервер).
view('home.welcome');
Набагато простіше читати чи читати, ніж:
var views = {};
views['home.welcome'] = new View('home.welcome');
view
Функція просто перевіряє , є чи той же вид вже існує в локальній карті. Якщо представлення не існує, воно створить новий вигляд і додасть новий запис на карту.
function view(name) {
if (!name) // Throw an error
if (view.views[name]) return view.views[name];
return view.views[name] = new View({
name: name
});
}
// Local Map
view.views = {};
Надзвичайно базовий, правда? Я вважаю, що це значно спрощує загальнодоступний інтерфейс і полегшує його використання. Я також використовую ланцюгові здібності ...
view('home.welcome')
.child('menus')
.child('auth')
Tower, рамка, яку я розробляю (з кимось іншим) або розробляю наступну версію (0.5.0), використовуватиме цей функціональний підхід у більшості своїх інтерфейсів, що розкривають.
Деякі люди використовують переваги волокон як спосіб уникнути "пекло зворотного дзвінка". Це зовсім інший підхід до JavaScript, і я не є його великим шанувальником, але багато фреймворків / платформ використовують його; включаючи Meteor, оскільки вони розглядають Node.js як потік / на платформу підключення.
Я вважаю за краще використовувати абстрагований метод, щоб уникнути пекла зворотного дзвінка. Це може стати громіздким, але це значно спрощує фактичний код програми. Допомагаючи будувати рамку TowerJS , вона вирішила багато наших проблем, хоча, очевидно, у вас все ще буде деякий рівень зворотних викликів, але вкладення не є глибоким.
// app/config/server/routes.js
App.Router = Tower.Router.extend({
root: Tower.Route.extend({
route: '/',
enter: function(context, next) {
context.postsController.page(1).all(function(error, posts) {
context.bootstrapData = {posts: posts};
next();
});
},
action: function(context, next) {
context.response.render('index', context);
next();
},
postRoutes: App.PostRoutes
})
});
Приклад нашої, що зараз розробляється, системи маршрутизації та "контролерів", хоча і досить відрізняється від традиційних "схожих на рейки". Але приклад надзвичайно потужний і мінімізує кількість зворотних дзвінків і робить речі досить очевидними.
Проблема такого підходу полягає в тому, що все абстрагується. Ніщо не працює як є, і не вимагає «рамки» за ним. Але якщо такі особливості та стиль кодування реалізовані в рамках, то це величезна виграш.
Для моделей у JavaScript це чесно залежить. Спадкування є корисним лише при використанні CoffeeScript, Ember або будь-якої «класової» структури / інфраструктури. Коли ви знаходитесь у "чистому" середовищі JavaScript, використання традиційного інтерфейсу прототипу працює як шарм:
function Controller() {
this.resource = get('resource');
}
Controller.prototype.index = function(req, res, next) {
next();
};
Ember.js почав, як мінімум, використовувати інший підхід до побудови об'єктів. Замість того щоб будувати кожен метод прототипу самостійно, ви використовуєте інтерфейс, схожий на модуль.
Ember.Controller.extend({
index: function() {
this.hello = 123;
},
constructor: function() {
console.log(123);
}
});
Все це різні стилі "кодування", але вони додають до вашої кодової бази.
Поліморфізм
Поліморфізм не широко застосовується в чистому JavaScript, де для роботи з успадкуванням та копіювання "класової" моделі потрібне багато кодового коду.
Дизайн на основі подій / компонентів
Моделі, що базуються на подіях та на компонентах, - це переможці IMO, або найпростіші з ними роботи, особливо при роботі з Node.js, який має вбудований компонент EventEmitter, хоча реалізація таких емітерів є тривіальною, це просто приємне доповнення .
event.on("update", function(){
this.component.ship.velocity = 0;
event.emit("change.ship.velocity");
});
Просто приклад, але це приємна модель роботи. Особливо в ігровому / компонентному проекті.
Конструкція компонентів сама по собі є окремою концепцією, але я думаю, що вона надзвичайно добре працює в поєднанні із системами подій. Ігри традиційно відомі за компонентним дизайном, де об’єктно-орієнтоване програмування відводить вас лише поки що.
Компонентна конструкція має її використання. Це залежить від типу системи вашої будівлі. Я впевнений, що він би працював з веб-додатками, але він дуже добре працював би в ігрових умовах через кількість об’єктів та окремих систем, але інші приклади, безумовно, існують.
Паб / під шаблон
Прив'язка подій та паб / суб є подібними. Шаблон pub / sub дійсно світить у додатках Node.js через мову, що об'єднує, але він може працювати в будь-якій мові. Дуже добре працює в додатках, іграх у реальному часі тощо.
model.subscribe("message", function(event){
console.log(event.params.message);
});
model.publish("message", {message: "Hello, World"});
Спостерігач
Це може бути суб'єктивним, оскільки деякі люди вважають, що модель спостерігача спостерігається як pub / sub, але вони мають свої відмінності.
"Спостерігач - це схема дизайну, де об'єкт (відомий як суб'єкт) підтримує список об'єктів залежно від нього (спостерігачів), автоматично повідомляючи їх про будь-які зміни стану." - Шаблон спостерігача
Шаблон спостерігачів - це крок за межі типових паб / підсистем. Об'єкти мають суворі стосунки або способи спілкування один з одним. Об'єкт "Тема" зберігатиме список утриманців "Спостерігачі". Тема постійно оновлюватиме своїх спостерігачів.
Реактивне програмування
Реактивне програмування - це менша, більш невідома концепція, особливо в JavaScript. Є одна рамка / бібліотека (про яку я знаю), яка дозволяє легко працювати з API, щоб використовувати це "реактивне програмування".
Ресурси реактивного програмування:
В основному, це набір синхронізуючих даних (будь-яких змінних, функцій тощо).
var a = 1;
var b = 2;
var c = a + b;
a = 2;
console.log(c); // should output 4
Я вважаю, що реактивне програмування значно приховано, особливо в імперативних мовах. Це надзвичайно потужна парадигма програмування, особливо в Node.js. "Метеор " створив власний реактивний двигун, в основі якого лежать основи. Як працює реактивність Метеора за лаштунками? це чудовий огляд того, як це працює всередині.
Meteor.autosubscribe(function() {
console.log("Hello " + Session.get("name"));
});
Це буде виконуватися нормально, відображаючи значення name
, але якщо ми змінимо його
Session.set ('ім'я', 'Bob');
Він повторно виведе відображення console.log Hello Bob
. Основний приклад, але ви можете застосувати цю методику до моделей даних та транзакцій у реальному часі. За цим протоколом можна створити надзвичайно потужні системи.
Метеори ...
Реактивна структура та модель спостерігача досить схожі. Основна відмінність полягає в тому, що модель спостерігача зазвичай описує потік даних цілими об'єктами / класами проти реактивного програмування, а натомість описує потік даних до певних властивостей.
Метеор - прекрасний приклад реактивного програмування. Виконання часу трохи складніше через відсутність у JavaScript JavaScript подій зміни цінності (проксі-сервери Harmony змінюють це). Інші рамки на стороні клієнта, Ember.js і AngularJS, також використовують реактивне програмування (певною мірою).
На пізніх двох фреймворках найчастіше використовується реактивний малюнок, особливо в їхніх шаблонах (тобто автоматичне оновлення). Angular.js використовує просту техніку брудної перевірки. Я б не назвав це точно реактивним програмуванням, але це близько, оскільки брудна перевірка не в режимі реального часу. Ember.js використовує інший підхід. Використання Ембер set()
та get()
методи, що дозволяють їм негайно оновлювати залежні значення. З їх біговим колесом він надзвичайно ефективний і дозволяє отримати більш залежні значення, де кутовий має теоретичну межу.
Обіцянки
Не виправляє зворотні дзвінки, але знімає деякий відступ і зводить вкладені функції до мінімуму. Це також додає непоганий синтаксис до проблеми.
fs.open("fs-promise.js", process.O_RDONLY).then(function(fd){
return fs.read(fd, 4096);
}).then(function(args){
util.puts(args[0]); // print the contents of the file
});
Ви також можете поширити функції зворотного виклику, щоб вони не були вбудованими, але це ще одне дизайнерське рішення.
Іншим підходом було б поєднання подій та обіцянок щодо того, де у вас буде функція відповідного відправлення подій, тоді реальні функціональні функції (ті, що мають реальну логіку в них) прив'язуються до певної події. Потім ви передасте метод диспетчера всередині кожної позиції зворотного виклику, однак, вам доведеться розробити деякі передумови, які б вам прийшли в голову, такі як параметри, знаючи, до якої функції потрібно відправити і т. Д. ...
Функція однієї функції
Замість того, щоб мати велике безладдя у зворотному звороті дзвінків, тримайте одну функцію в одному завданні та виконайте це завдання добре. Іноді ви можете випереджати себе і додавати більше функціональності в межах кожної функції, але запитайте себе: чи може це стати незалежною функцією? Назвіть функцію, і це очистить ваш відступ і, як результат, очистить пекельну проблему зворотного дзвінка.
Зрештою, я б запропонував розробити або використовувати невеликий "фреймворк", в основному, основу для вашої програми, і витратити час на те, щоб зробити абстракції, визначитися з системою, що базується на подіях, або "безліччю невеликих модулів, які є незалежна "система. Я працював з декількома проектами Node.js, де код був надзвичайно брудним, зокрема, пекельним зворотним дзвоном, а також відсутністю думки перед тим, як почати кодування. Знайдіть свій час, щоб продумати різні можливості з точки зору API та синтаксису.
Бен Надель створив кілька справді хороших публікацій у блозі про JavaScript та деякі досить строгі та вдосконалені зразки, які можуть працювати у вашій ситуації. Деякі хороші пости, на які я наголошу:
Інверсія управління
Хоча це не зовсім пов’язано з пекельним зворотним дзвоном, це може допомогти вам загальною архітектурою, особливо в одиничних тестах.
Дві основні підверсії інверсії керування - це впорскування та локатор обслуговування. Мені здається, що Локатор послуг є найпростішим в JavaScript, на відміну від введення залежностей. Чому? Головним чином, тому що JavaScript - це динамічна мова і не існує статичного введення тексту. Java та C #, серед інших, "відомі" для ін'єкцій залежностей, оскільки вони здатні виявляти типи, і вони мають вбудовані інтерфейси, класи тощо ... Це робить речі досить простими. Однак ви можете заново створити цю функціональність у JavaScript, хоча це не буде ідентичним і трохи хитким, я вважаю за краще використовувати сервіс-локатор всередині моїх систем.
Будь-який вид інверсії управління різко роз’єднає ваш код на окремі модулі, які можна будь-коли знущати або підробляти. Розроблено другу версію вашого двигуна візуалізації? Чудово, просто замініть старий інтерфейс на новий. Локатори сервісів особливо цікаві з новими проксі-програмами Harmony, хоча, вони ефективно використовуються лише в Node.js, вони надають більш приємний API, а не використання Service.get('render');
та замість цього Service.render
. Зараз я працюю над такою системою: https://github.com/TheHydroImpulse/Ettore .
Хоча відсутність статичного набору тексту (статичне введення є можливою причиною ефективних звичаїв введення залежностей на Java, C #, PHP - це не статичне введення, але воно має підказки типу). безумовно перетворіть це на сильну точку. Оскільки все динамічно, ви можете створити "підроблену" статичну систему. У поєднанні з локатором сервісу, ви можете мати кожен компонент / модуль / клас / екземпляр, прив'язаний до типу.
var Service, componentA;
function Manager() {
this.instances = {};
}
Manager.prototype.get = function(name) {
return this.instances[name];
};
Manager.prototype.set = function(name, value) {
this.instances[name] = value;
};
Service = new Manager();
componentA = {
type: "ship",
value: new Ship()
};
Service.set('componentA', componentA);
// DI
function World(ship) {
if (ship === Service.matchType('ship', ship))
this.ship = new ship();
else
throw Error("Wrong type passed.");
}
// Use Case:
var worldInstance = new World(Service.get('componentA'));
Спрощений приклад. Для реального світу, ефективного використання, вам потрібно буде продовжити цю концепцію, але це може допомогти роз'єднати вашу систему, якщо ви дійсно хочете традиційного введення залежності. Можливо, вам доведеться трохи познайомитися з цією концепцією. Я не дуже багато розмірковував у попередньому прикладі.
Модель-перегляд-контролер
Найбільш очевидний зразок і найбільш використовуваний в Інтернеті. Кілька років тому в JQuery була вся ярость, і так, народилися плагіни JQuery. Вам не потрібна повна рамка на стороні клієнта, просто використовуйте jquery та кілька плагінів.
Тепер існує величезна рамкова війна JavaScript на стороні клієнта. Більшість з них використовує шаблон MVC, і всі вони використовують його по-різному. MVC не завжди реалізується однаково.
Якщо ви використовуєте традиційні прототипові інтерфейси, вам може бути важко отримати синтаксичний цукор або приємний API при роботі з MVC, якщо ви не хочете виконати ручну роботу. Ember.js вирішує це, створюючи систему "клас" / об'єкт ". Контролер може виглядати так:
var Controller = Ember.Controller.extend({
index: function() {
// Do something....
}
});
Більшість бібліотек на стороні клієнта також розширюють шаблон MVC, вводячи помічників перегляду (перетворюючись у представлення) та шаблонів (перетворюючись у представлення).
Нові можливості JavaScript:
Це буде ефективно лише в тому випадку, якщо ви використовуєте Node.js, але, тим не менш, це неоціненно. Ця бесіда в NodeConf від Brendan Eich приносить кілька цікавих нових можливостей. Запропонований синтаксис функції та, особливо, бібліотека Task.js js.
Це, ймовірно, виправить більшість проблем із вкладенням функцій та принесе трохи кращі показники роботи через відсутність накладних функцій.
Я не надто впевнений, чи V8 підтримує це споконвічно, востаннє я перевірив, що вам потрібно включити деякі прапори, але це працює у порту Node.js, який використовує SpiderMonkey .
Додаткові ресурси: