Недавно столкнулся со следующей задачей — существует JavaScript объект actions с кучей методов, в каждом из которых должны быть определены следующие переменные:
Но определять их очень не хотелось, так как во-первых от этого заметно утяжелится код, а во-вторых объект actions создавался для будущих пользователей моего фреймворка Persik, и как предполагается, каждый сможет не только применять готовые методы actions, но и определять свои. Можно конечно написать в документации «ребята, пожалуйста, в начале каждого создаваемого вами метода вставьте вот эти строчки», либо не создавать ни каких переменных вообще и вместо persik писать this (хотя с this в некоторых случаях могут быть проблемы), вместо actions писать this.actions, а вместо вызова next() ляпнуть что-нибудь вроде:
if (this.dom.возможноЕщёЧтоТо.имяЭтогоМетода.next) this.dom.возможноЕщёЧтоТо.имяЭтогоМетода.next();
Но всё это ужасно не красиво и не правильно, и я решил пойти по другому пути. Идея проста — при инициализации фреймворка пройтись по всем методам объекта actions, с помощью toString() получить каждый метод как строку, нагло вставить туда нужные строчки определения переменных, сделать из строки снова функцию с помощью eval() и заменить существующий метод модифицированным. Не буду приводить исходный код поиска методов внутри actions в моём фреймворке дабы не утомлять публику, а вот модификация методов выглядит примерно следующим образом:
Здесь func — это собственно модифицируемый метод, в funcName передаётся имя этого метода, причём учитывая всех родителей, т.е. то самое this.dom.возможноЕщёЧтоТо.имяЭтогоМетода, а в variablesStr содержится строка с переменными, которые мы определяем. В моём случае это 'var persik = this; var actions = persik.actions; var next = ([funcName].next)? [funcName].next: function(){return false}; '. Описанная выше функция заменяет в variablesStr все '[funcName]' на funcName, вставляет variablesStr в resultFunc после первого символа '{', собирает функцию из строки снова и отдаёт обратно. Объект actions, над которым производятся все вышеперечисленные извращения, можно посмотреть здесь: nikitaeremin.com/js/action.js
Данная техника имеет ряд минусов:
— переменные persik, actions и next создаются во всех методах объекта actions, хотя в некоторых методах они могут остаться не востребованными;
— меняется исходный код JavaScript и при возникновении ошибок периода исполнения FireBug будет ругаться совсем не на те строки, которые в действительности вызвали ошибку, хотя при этом причину ошибки говорит правильно;
— остальные минусы мне сейчас хабраобщественность скажет :)
Не думаю, что описанный выше трюк применим к широкому кругу задач, эта статья скорее для общего развития, но если кто-нибудь захочет поделиться идеями по поводу других областей применения подобных инъекций, пишите на мыло: nikitaeremin[at]gmail.com
var persik = this, actions = persik.actions, next = ( persik.dom.возможноЕщёЧтоТо.имяЭтогоМетода.next)? persik.dom.возможноЕщёЧтоТо.имяЭтогоМетода.next : function(){return false};
Но определять их очень не хотелось, так как во-первых от этого заметно утяжелится код, а во-вторых объект actions создавался для будущих пользователей моего фреймворка Persik, и как предполагается, каждый сможет не только применять готовые методы actions, но и определять свои. Можно конечно написать в документации «ребята, пожалуйста, в начале каждого создаваемого вами метода вставьте вот эти строчки», либо не создавать ни каких переменных вообще и вместо persik писать this (хотя с this в некоторых случаях могут быть проблемы), вместо actions писать this.actions, а вместо вызова next() ляпнуть что-нибудь вроде:
if (this.dom.возможноЕщёЧтоТо.имяЭтогоМетода.next) this.dom.возможноЕщёЧтоТо.имяЭтогоМетода.next();
Но всё это ужасно не красиво и не правильно, и я решил пойти по другому пути. Идея проста — при инициализации фреймворка пройтись по всем методам объекта actions, с помощью toString() получить каждый метод как строку, нагло вставить туда нужные строчки определения переменных, сделать из строки снова функцию с помощью eval() и заменить существующий метод модифицированным. Не буду приводить исходный код поиска методов внутри actions в моём фреймворке дабы не утомлять публику, а вот модификация методов выглядит примерно следующим образом:
_rewriteFunc : function(func, funcName, variablesStr){ var funcArr = func.toString().split('{'); variablesStr = variablesStr.replace(/\[funcName\]/g, funcName); funcArr[1] = variablesStr + funcArr[1]; var resultFunc = funcArr.join('{'); eval ('var a = ' + resultFunc); return a; },
Здесь func — это собственно модифицируемый метод, в funcName передаётся имя этого метода, причём учитывая всех родителей, т.е. то самое this.dom.возможноЕщёЧтоТо.имяЭтогоМетода, а в variablesStr содержится строка с переменными, которые мы определяем. В моём случае это 'var persik = this; var actions = persik.actions; var next = ([funcName].next)? [funcName].next: function(){return false}; '. Описанная выше функция заменяет в variablesStr все '[funcName]' на funcName, вставляет variablesStr в resultFunc после первого символа '{', собирает функцию из строки снова и отдаёт обратно. Объект actions, над которым производятся все вышеперечисленные извращения, можно посмотреть здесь: nikitaeremin.com/js/action.js
Данная техника имеет ряд минусов:
— переменные persik, actions и next создаются во всех методах объекта actions, хотя в некоторых методах они могут остаться не востребованными;
— меняется исходный код JavaScript и при возникновении ошибок периода исполнения FireBug будет ругаться совсем не на те строки, которые в действительности вызвали ошибку, хотя при этом причину ошибки говорит правильно;
— остальные минусы мне сейчас хабраобщественность скажет :)
Не думаю, что описанный выше трюк применим к широкому кругу задач, эта статья скорее для общего развития, но если кто-нибудь захочет поделиться идеями по поводу других областей применения подобных инъекций, пишите на мыло: nikitaeremin[at]gmail.com