Стандартную программу на языке программирования можно представить в виде набора инструкций и набора данных обрабатываемые этими инструкциями. Поэтому суть метапрограммирования можно выразить в концепции - инструкции это тоже данные, которые можно обрабатывать.
Чтобы погрузится в глубины метопрограммирования на JS надо разобраться с типами данными и с тем, что они из себя представляют.
Практическая ценность
Подумаем, зачем это может понадобится?
В тексте статьи предлагается объект MetaObject, который представляет облегченный инструмент для изучения доступных свойств объектов — полей и методов. Приводится возможность модификации свойств объектов, внесение новых методов в прототип объекта. Так же приводится краткое изложение основных принципов объектной модели Javascript.
Поехали, в JS всего навсего 6 типов данных:
1.undefined
2.null
3.number
4.string
5.boolean
6.object
Что такое undefined и null.
The undefined value is a primitive value used when a variable has not been assigned a value.
undefined — пустое значение.
The null value is a primitive value that represents the null, empty, or non-existent reference
null — пустая ссылка, ссылка на спец объект, который бросается эксепшенами, когда обращаются к его свойствам/методам.
Что такое объекты в javascript в нескольких тезисах и пояснениях:
1) Любой объект имеет ссылку на объект-прототип (в редких случаях эта ссылка может указывать на null) функции которая создала этот объект.
Объекты в Mozilla/WebKit/KHtml имеют свойство __proto__ , который ссылается на прототип объекта функции создавшей этот объект. Прототип объекта, по умолчанию, имеет свойство constructor, которая ссылается на искомую функцию. В Internet Explorer свойства __proto__ нет но есть доступ к методу constructor и его прототипу.
2) При доступе к свойству объекта на чтение или на вызов (для методов), если оно не найдено в самом объекте, то ищется в объекте-прототипе, и далее по цепочке.
3) Функция — тоже объект. Числа и строки не являются объектами, но конвертируются в них автоматически в момент вызова методов ('test'.charAt(0) == new String('test').charAt(0)).
4) Любой объект явно или неявно создается с помощью конструкции new и функции-конструктора ([1,2,3] == new Array(1,2,3)).
5) Любая функция имеет свойство prototype, которое как раз и содержит ссылку на объект, используемый в качестве прототипа при создании новых объектов (см. пункт 4). Таким образом — любая функция является конструктором.
//Рассмотрим механизм наследования свойств
function Foo() { } ;
var f1 = new Foo();
Foo.prototype.x = "hello";
f1.x //=> hello
Foo.x //=> undedfine
//Еще один пример на тему — любая функция является конструктором
function foo() { } ;
var f1 = new foo();
f1.constructor === foo.prototype.constructor === foo
//Заменим объект prototype:
foo.prototype = new Object();
//Что получилось
f1.constructor === foo.prototype.constructor === Object
//so now we say:
foo.prototype.constructor == foo
//all is well again
f1.constructor === foo.prototype.constructor === foo
6) Object, Function, Array, RegExp, String, Number и т.д. — это функции.
7) У любого объекта в начале цепочки прототипов стоит Object.prototype (кроме случаев когда у объекта вообще нет прототипа). У самого Object.prototype ссылка на объект-прототип равна null.
8) Все объекты автоматически принимают свойства объекта прототипа, как если бы они были объявлены до создания этих объектов. Принятие одноименного свойства с свойством прототипа объекта приводит к потери последнего:
function foo() { }
f1 = new foo();
f2 = new foo();
foo.prototype.x = "hello";
f1.x = "hello"
f2.x = "hello";
f1.x = "goodbye"; //свойство f1.x скрывает foo.prototype.x
f1.x = "goodbye" //скрываем "hello" для экземпляра f1 только
f2.x = "hello"
delete f1.x
f1.x = "hello"; //foo.prototype.x снова доступен для f1.
Пример использования метапрограммирования в Javascript.
Бывают случаи, когда во время работы или отладки необходимо знать какими методами и полями обладает какой - либо объект:
$('#button').click(function(e){...})
или
element.onclick=function(e){...}
В первом и во втором случае обработчики события принимают в качестве значение объект e, какие они свойства имеют, меняется ли набор свойств от браузера к браузеру ???
Для таких случаев был разработан объект MetaObject, способный ответить на эти вопросы.
В объекте всего три метода:
toString() - вывод всех свойств объектов
toFullString() - вывод всех свойств объектов с указанием их значений
feature(property_name, property_value) - метод для непосредственного изменения объектов, удаление|добавление свойств объектов.
//Создадим тестовый объект
function testObj(){
this.field1="mes1";
this.field2=19;
}
testObj.prototype.method1=function(){
if(console.log!=undefined)
console.log("method1")
}
testObj.prototype.prop1={
"field1":"aaa",
"field2":12
}
//Создадим экземпляр тестового объекта
var obj1=new testObj()
//Создадим экземпляр объекта MetaObject,
// в качестве параметра принимается экземпляр тестового объекта
var objInfo1=new MetaObject(obj1);
window.onload=function(){
//### Добавление и вызов методов ###
//Объекту можно добавить свои методы:
objInfo1.feature("method2",function(){console.log("it is method2")})
objInfo1.feature("method3", new Function("x", "y", "alert(x*y)"))
objInfo1.feature("method4", new Function("x,y", "return x * y"))
//Вызываем свеже-созданный метод:
obj1.method2()
obj1.method3(3,2)
console.log(obj1.method4(9,4))
//Вызов метода передачей имени метода литерной константой:
objInfo1.obj["method2"]()
//#### Изучаем свойства объекта ###
//Получаем код метода:
console.log("Code of method1: %s",objInfo1.obj["method1"].toString())
// 0: Конструктор Объекта (Доступен из объекта прототипа)
console.log("Конструктор объекта: "+objInfo1.obj["constructor"].toString())
// 1: Определим тип объекта прототипа
// свойства __proto__ нет в IE
if (objInfo1.obj["__proto__"]!=null){
console.log("Find type of prototype object: %s",objInfo1.obj["__proto__"].toString())
}else{
console.log("Слава Internet Explorer : "+objInfo1.obj["constructor"].toString())
}
// 2: Выведем свойства объекта прототипа:
var protoMetaObject=new MetaObject(objInfo1.obj["__proto__"]);
console.log("Select all proto fields && methods: %s",protoMetaObject.toString())
// 3: Получаем конструктор прототипа объекта - код функции:
console.log("Constructor of current object (source):\n%s",objInfo1.obj["__proto__"]["constructor"].toString())
//Внедрим в прототип объекта новое свойство поле pirat_field:
objInfo1.obj["__proto__"]["constructor"]["prototype"]["pirat_field"]="wowww"
//Замечания:
// 1) Чтобы внедрить новые свойства достаточно использовать сам объект:
obj1["__proto__"]["constructor"]["prototype"]["pirat_field2"]="wow2"
// 2) И не обязательно использовать свойство __proto__, которое не поддерживается IE:
obj1["constructor"]["prototype"]["pirat_field3"]="wow3"
//Все свойства объекта после махинаций
console.log("Fields & Methods at the end:\n%s",objInfo1.toString())
//С помощью MetaObject выведем все доступные методы и поля
var str=objInfo1.toString()
var testNode=document.getElementById('test1')
testNode.appendChild(document.createTextNode(str))
// и просто выведем их в окне в месте с содержимым:
alert(objInfo1.toFullString())
}
Рис. 1 Результат работы
Другое возможное применение метапрограммирования - слияние экземпляров объекта
//Функция слияния двух объектов mergeLeftObject
//конечный объект - это первый задаваемый объект с уникальными свойствами из второго объекта
//(т.е. дубликаты из второго объекта не вставляются)
function mergeLObj(obj1,obj2){
var obj3=obj1
for(key in obj2){
if(!obj3[key]){
obj3[key]=obj2[key]
}
}
return obj3
}
console.log('Слияние двух объектов')
// 1-й объект
var dobj1={
"field1":"aaa",
"field2":12
}
// 2-й объект
var dobj2={
"field3":"bbb",
"field4":123
}
// 2-объект скрещиваем с первым и получаем третий объект
var dobj12=mergeLObj(dobj1, dobj2)
//Смотрим на результат
var dobjInfo = new MetaObject(dobj12)
console.log(dobjInfo.toFullString())