Mitä on metaprogrammointi JavaScriptissa? Englanniksi Kiitos.
JavaScriptillä on monia hyödyllisiä ominaisuuksia, joista useimmat kehittäjät tietävät. Samalla on joitain piilotettuja helmiä, jotka voivat ratkaista todella haastavat ongelmat, jos tiedät niistä.
Metaprogrammit JavaScript-muodossa on sellainen käsite, jota monet meistä eivät ehkä tunne. Tässä artikkelissa opit Metaprogrammingista ja sen hyödyllisyydestä meille.
ES6: n (ECMAScript 2015) avulla meillä on tuki kohteille Reflect
ja Proxy
kohteille, joiden avulla voimme tehdä metaprogrammit helposti. Tässä artikkelissa opit käyttämään niitä esimerkkien avulla.
Mikä on metaprogrammointi?
Metaprogramming
ei ole mitään muuta kuin taika ohjelmoinnissa ! Entä kirjoittaa ohjelma, joka lukee, muokkaa, analysoi ja jopa luo ohjelman? Eikö se kuulosta velhomaisesti ja voimakkaasti?

Näin kuvaan Metaprogrammingia kehittäjänä, joka käyttää sitä koko ajan:
Metaprogramming
on ohjelmointitekniikka, jossa tietokoneohjelmilla on kyky kohdella muita ohjelmia datana. Tämä tarkoittaa, että ohjelma voidaan suunnitella lukemaan, luomaan, analysoimaan tai muuntamaan muita ohjelmia ja jopa muokkaamaan itseään käynnissä.
Yksinkertaisesti sanottuna, metaprogrammiin liittyy koodin kirjoittaminen, joka voi
- Luo koodi
- Manipuloi kielirakenteita ajon aikana. Tämä ilmiö tunnetaan
Reflective Metaprogramming
taiReflection
.
Mikä on heijastus metaprogrammeerinnassa?
Reflection
on Metaprogrammingin haara. Reflektiolla on kolme alaosaa:
- Itsetarkastus : Code pystyy tarkastamaan itsensä. Sitä käytetään koodin erittäin matalan tason tietojen löytämiseen.
- Itsemuutos : Kuten nimestä voi päätellä, koodi pystyy muokkaamaan itseään.
- Esirukous : Toimi jonkun muun puolesta. Tämä voidaan saavuttaa käärimällä, ansastamalla, sieppaamalla.
ES6 antaa meille Reflect
tavoitteen (alias Reflect API), jonka haluamme saavuttaa Introspection
. Proxy
Tavoitteena ES6 auttaa meitä Intercession
. Emme puhu liikaa, Self-Modification
koska haluamme pysyä poissa siitä mahdollisimman paljon.
Odota hetki! Ainoastaan selvyyden vuoksi metaprogrammointia ei otettu käyttöön ES6: ssa. Pikemminkin se on ollut saatavana kielellä alusta asti. ES6: n ansiosta sen käyttö on paljon helpompaa.
Metaprogrammointi ennen ES6-aikakautta
Muistatko eval
? Katsotaanpa, miten sitä käytettiin:
const blog = { name: 'freeCodeCamp' } console.log('Before eval:', blog); const key = 'author'; const value = 'Tapas'; testEval = () => eval(`blog.${key} = '${value}'`); // Call the function testEval(); console.log('After eval magic:', blog);
Kuten huomaat, eval
auttoi koodin luomisessa. Tässä tapauksessa objektia blog
on muokattu lisäominaisuudella suorituksen aikana.
Before eval: {name: freeCodeCamp} After eval magic: {name: "freeCodeCamp", author: "Tapas"}
Itsetarkastus
Ennen Reflect object
ES6: n sisällyttämistä voisimme silti tehdä itsetarkastuksen. Tässä on esimerkki ohjelman rakenteen lukemisesta:
var users = { 'Tom': 32, 'Bill': 50, 'Sam': 65 }; Object.keys(users).forEach(name => { const age = users[name]; console.log(`User ${name} is ${age} years old!`); });
Tässä luetaan users
objektirakenne ja kirjataan avaimen arvo lauseeseen.
User Tom is 32 years old! User Bill is 50 years old! User Sam is 65 years old!
Itsemuutos
Otetaan blogiobjekti, jolla on tapa muuttaa itseään:
var blog = { name: 'freeCodeCamp', modifySelf: function(key, value) {blog[key] = value} }
blog
Esine voi muuttaa itseään tekemällä näin:
blog.modifySelf('author', 'Tapas');
Esirukous
Intercession
on toimia jonkin muun puolesta muuttamalla kielen semantiikkaa. Object.defineProperty()
Menetelmä voi muuttaa objektin semantiikka:
var sun = {}; Object.defineProperty(sun, 'rises', { value: true, configurable: false, writable: false, enumerable: false }); console.log('sun rises', sun.rises); sun.rises = false; console.log('sun rises', sun.rises);
Tuotos,
sun rises true sun rises true
Kuten näette, sun
objekti luotiin normaalina objektina ja sitten semanttia on muutettu niin, että sitä ei voida kirjoittaa.
Hyppäämme nyt ymmärtämään Reflect
ja Proxy
esineitä niiden käytöillä.
Reflect-sovellusliittymä
ES6: ssa Reflect on uusi Global Object
(kuten matematiikka), joka tarjoaa useita apuohjelmatoimintoja, joista monet näyttävät olevan päällekkäisiä globaalisti määriteltyjen ES5-menetelmien kanssa Object
.
Kaikki nämä toiminnot ovat itsetarkastustoimintoja, joissa voit kysyä joitain sisäisiä tietoja ohjelmasta ajon aikana.
Tässä on luettelo Reflect
objektista käytettävissä olevista menetelmistä . Käy tällä sivulla saadaksesi lisätietoja kustakin näistä menetelmistä.
// Reflect object methods Reflect.apply() Reflect.construct() Reflect.get() Reflect.has() Reflect.ownKeys() Reflect.set() Reflect.setPrototypeOf() Reflect.defineProperty() Reflect.deleteProperty() Reflect.getOwnPropertyDescriptor() Reflect.getPrototypeOf() Reflect.isExtensible()
Mutta odota, tässä on kysymys: Miksi tarvitsemme uuden API-objektin, kun ne voivat olla jo olemassa tai ne voidaan lisätä Object
tai Function
?
Hämmentynyt? Yritetään selvittää tämä.
Kaikki yhdessä nimiavaruudessa
JavaScriptillä oli jo tuki objektien heijastukselle. Mutta näitä sovellusliittymiä ei ole järjestetty yhteen nimitilaan. ES6: n jälkeen he ovat nyt alle Reflect
.
Toisin kuin useimmat globaalit objektit, Reflect ei ole konstruktori. Et voi käyttää sitä uuden operaattorin kanssa tai kutsua Reflect-objektia toimintona. Kaikki ominaisuudet ja menetelmät Reflect
ovat static
kuin matematiikkaobjekti.
Helppo käyttää
introspection
Menetelmät Object
heittää poikkeus, kun ne eivät toiminnon suorittamiseen. Tämä on lisätaakka kuluttajalle (ohjelmoijalle) käsitellä kyseistä poikkeusta koodissa.
You may prefer to handle it as a boolean(true | false)
instead of using exception handling. The Reflect object helps you do that.
Here's an example with Object.defineProperty:
try { Object.defineProperty(obj, name, desc); // property defined successfully } catch (e) { // possible failure and need to do something about it }
And with the Reflect API:
if (Reflect.defineProperty(obj, name, desc)) { // success } else { // failure (and far better) }
The impression of the First-Class operation
We can find the existence of a property for an object as (prop in obj)
. If we need to use it multiple times in our code, we must explicitly wrap this operation in a function and pass the operation around as a first-class value.
In ES6, we already had those as part of the Reflect API
as the first-class function. For example, Reflect.has(obj, prop) is the functional equivalent of (prop in obj).
Let's look at another example: Delete an object property.
const obj = { bar: true, baz: false}; // delete object[key] function deleteProperty(object, key) { delete object[key]; } deleteProperty(obj, 'bar');
With the Reflect API:
// With Reflect API Reflect.deleteProperty(obj, 'bar');
A more reliable way of using the apply() method
In ES5, we can use the apply()
method to call a function with a given this
value and passing an array as an argument.
Function.prototype.apply.call(func, obj, arr); // or func.apply(obj, arr);
This is less reliable because func
could be an object that would have defined its own apply
method.
In ES6 we have a more reliable and elegant way of solving this:
Reflect.apply(func, obj, arr);
In this case, we will get a TypeError
if func
is not callable. Also, Reflect.apply()
is less verbose and easier to understand.
Helping other kinds of reflection
Wewill see what this means in a bit when we learn about the Proxy
object. The Reflect API methods can be used with Proxy in many use cases.
The Proxy Object
ES6's Proxy
object helps in intercession
.
The proxy
object defines custom behaviors for fundamental operations (for example, property lookup, assignment, enumeration, function invocation, and so on).
Here are a few useful terms you need to remember and use:
- The
target
: An object which the proxy virtualizes. - The
handler
: A placeholder object which contains traps. - The
trap
: Methods that provide property access to the target object.
It is perfectly fine if you don't quite understand yet from the description above. We will get a grasp of it through code and examples in a minute.
The syntax to create a Proxy object is as follows:
let proxy = new Proxy(target, handler);
There are many proxy traps (handler functions) available to access and customize a target object. Here is the list of them. You can read a more detailed description of traps here.
handler.apply() handler.construct() handler.get() handler.has() handler.ownKeys() handler.set() handler.setPrototypeOf() handler.getPrototypeOf() handler.defineProperty() handler.deleteProperty() handler.getOwnPropertyDescriptor() handler.preventExtensions() handler.isExtensible()
Note that each of the traps has a mapping with the Reflect
object's methods. This means that you can use Reflect
and Proxy
together in many use cases.
How to get unavailable object property values
Let's look at an example of an employee
object and try to print some of its properties:
const employee = { firstName: 'Tapas', lastName: 'Adhikary' }; console.log(employee.firstName); console.log(employee.lastName); console.log(employee.org); console.log(employee.fullName);
The expected output is the following:
Tapas Adhikary undefined undefined
Now let's use the Proxy object to add some custom behavior to the employee
object.
Step 1: Create a Handler that uses a get trap
We will use a trap called get
which lets us get a property value. Here is our handler:
let handler = { get: function(target, fieldName) { if(fieldName === 'fullName' ) { return `${target.firstName} ${target.lastName}`; } return fieldName in target ? target[fieldName] : `No such property as, '${fieldName}'!` } };
The above handler helps to create the value for the fullName
property. It also adds a better error message when an object property is missing.
Step 2: Create a Proxy Object
As we have the target employee
object and the handler, we will be able to create a Proxy object like this:
let proxy = new Proxy(employee, handler);
Step 3: Access the properties on the Proxy object
Now we can access the employee object properties using the proxy object, like this:
console.log(proxy.firstName); console.log(proxy.lastName); console.log(proxy.org); console.log(proxy.fullName);
The output will be:
Tapas Adhikary No such property as, 'org'! Tapas Adhikary
Notice how we have magically changed things for the employee
object!
Proxy for Validation of Values
Let's create a proxy object to validate an integer value.
Step 1: Create a handler that uses a set trap
The handler looks like this:
const validator = { set: function(obj, prop, value) { if (prop === 'age') { if(!Number.isInteger(value)) { throw new TypeError('Age is always an Integer, Please Correct it!'); } if(value < 0) { throw new TypeError('This is insane, a negative age?'); } } } };
Step 2: Create a Proxy Object
Create a proxy object like this:
let proxy = new Proxy(employee, validator);
Step 3: Assign a non-integer value to a property, say, age
Try doing this:
proxy.age = 'I am testing a blunder'; // string value
The output will be like this:
TypeError: Age is always an Integer, Please Correct it! at Object.set (E:\Projects\KOSS\metaprogramming\js-mtprog\proxy\userSetProxy.js:28:23) at Object. (E:\Projects\KOSS\metaprogramming\js-mtprog\proxy\userSetProxy.js:40:7) at Module._compile (module.js:652:30) at Object.Module._extensions..js (module.js:663:10) at Module.load (module.js:565:32) at tryModuleLoad (module.js:505:12) at Function.Module._load (module.js:497:3) at Function.Module.runMain (module.js:693:10) at startup (bootstrap_node.js:188:16) at bootstrap_node.js:609:3
Similarly, try doing this:
p.age = -1; // will result in error
How to use Proxy and Reflect together
Here is an example of a handler where we use methods from the Reflect API:
const employee = { firstName: 'Tapas', lastName: 'Adhikary' }; let logHandler = { get: function(target, fieldName) { console.log("Log: ", target[fieldName]); // Use the get method of the Reflect object return Reflect.get(target, fieldName); } }; let func = () => { let p = new Proxy(employee, logHandler); p.firstName; p.lastName; }; func();
A few more Proxy use cases
There are several other use-cases where this concept can be used.
- To protect the ID field of an object from deletion (trap: deleteProperty)
- To trace Property Accesses (trap: get, set)
- For Data Binding (trap: set)
- With revocable references
- To manipulate the
in
operator behavior
... and many more.
Metaprogramming Pitfalls
While the concept of Metaprogramming
gives us lots of power, the magic of it can go the wrong way sometimes.

Be careful of:
- Too much
magic
! Make sure you understand it before you apply it. - Possible performance hits when you're making the impossible possible
- Could be seen as counter-debugging.
In Summary
To summarize,
Reflect
andProxy
are great inclusions in JavaScript to help with Metaprogramming.- Lots of complex situations can be handled with their help.
- Ole tietoinen myös haittapuolista.
- ES6-symboleja voidaan käyttää myös olemassa olevien luokkien ja objektien kanssa niiden käyttäytymisen muuttamiseksi.
Toivon, että pidit tämän artikkelin oivaltavaksi. Kaikki tässä artikkelissa käytetyt lähdekoodit löytyvät GitHub-arkistostani.
Jaa artikkeli, jotta myös muut voivat lukea sen. Voit @ minua Twitterissä (@tapasadhikary) kommenteilla tai voit seurata minua.