DEV Community

Daniel Castro
Daniel Castro

Posted on

4 JavaScript things you need to know

As JavaScript Developer I have found a lot of interesting stuff, I'd like share with you 4 things that I think you should know if you want to be a better JavaScript developer.

.call(), .bind(), .apply()

If you have worked in React any time, I'm sure you have seen bind() method, maybe you have used it without know what it really means. Maybe you have seen some these methods in a some JavaScript library and you don't understand how call(), bind(), apply() it works.

The first thing you need to understand is what this means. thisrefers in memory to the object reference of the current context and the reference to where it refers can change according to where the execution phase of a context is executing.

This methods allow us to change the reference to where this refers.

.bind()

const user = {
  name: "Peter",
  lastName: "Parker",
  fullName: function () {
    return `${this.name} ${this.lastName}`;
  },
};
const print = function (greet) {
  console.log(`${greet} ${this.fullName()}`);
};

print("hi");
Enter fullscreen mode Exit fullscreen mode

When we execute this code, we will get a error: this.fullname() is not a function because in print function this refers to global object, If we want to access the user context inside the print function, we can use the bind method, like this:

const myBindedPrint = print.bind(user);
myBindedPrint("hi");
Enter fullscreen mode Exit fullscreen mode

So, what did we do? Well, we created a copy of print function and save it in our myBindedPrint variable. So, bind() method allows us to create a copy with a special feature, we can pass as parameter the context where we want this to refers.

.call()

.call() executes a function, like if we are using () and it allows us to pass as first parameter the reference to where should refer this.

const user = {
  name: "Peter",
  lastName: "Parker",
  fullName: function () {
    return `${this.name} ${this.lastName}`;
  },
};

print.call(user, "hello");
Enter fullscreen mode Exit fullscreen mode

When we execute that, we get same result like when we use .bind(), the difference is that when we use .call() we don't create a copy, we just execute it.

apply()

Well, there's a single difference between call() and apply() and it is the way how we call it. .apply() method receives parameters as a array, like this.

print.apply(user, ['hello'])
Enter fullscreen mode Exit fullscreen mode

In which case can we use it?

Function borrowing

When we want to share functions between different objects. Some thing like "borrow" a function to another object. Let's see a example.

const user = {
  name: "Peter",
  lastName: "Parker",
  getFullName: function () {
    return `${this.name} ${this.lastName}`;
  },
};
const dog = {
  name: "Thoby",
  lastName: "Parker",
};
const result = user.getFullName.apply(dog);
console.log(result);
Enter fullscreen mode Exit fullscreen mode

bind (), call () and apply () have to do with the reference to where this refers, in each context, that is why it is useful, since with these tools we can decide what this means, it is powerful when solving problems related to the contexts of execution.

Multiples Promise execution

I have seen in beginners programmers a common mistake.

Let's imagine we have a method which call our API in order to save a product in a database, it is a asynchronous operation. Now, let's imagine we have a array products and we want to save them and wait until that to be executed to continue to do anything, whatever. we would need to execute several promises. I have seen something like this.

const saveProduct = () =>
  new Promise((resolve, reject) => {
    setTimeout(() => resolve({ data: {}, success: true }), 3000);
  });

const saveProducts = async (products) => {
  try {
    const response = await products.map(
      async (product) => await saveProduct(product)
    );
    console.log("success");
    return response;
  } catch (err) {
    console.log(err);
  }
};
const products = [{ name: "Pollo" }, { name: "Cerveza" }, { name: "Agua" }];
saveProducts(products).then((response) => console.log("response", response));
Enter fullscreen mode Exit fullscreen mode

Maybe it seems to make sense, but this line => console.log("success") will be executed immediately, check it!.

What you need to do, is something like this.

const saveProducts = async (products) => {
  try {
    const response = await Promise.all(
      products.map((product) => saveProduct(product))
    );
    console.log("succes");
    return response;
  } catch (err) {
    console.log(err);
  }
};
Enter fullscreen mode Exit fullscreen mode

If you execute it you will see it works as we expected. Our line => console.log("success") is not executed until all promise are resolved. Also, our saveProducts method returns all responses of our Promises.

Proxy

ES2015 proxies provide an API to catch or intercept any operation performed on an object and to modify how that object behaves. JavaScript proxies are useful for many things, such as:

  • Interception
  • Object virtualization.
  • Resource management.
  • Profiling and generating logs while debugging an application.
  • Security and access control.

To implement a proxy you need to know some terms.

  • target: the original object which you want to proxy.
  • handler: an object that defines which operations will be intercepted and how to redefine intercepted operations.

Let's see a example.

const person = {
  name: "Peter",
};

const handler = {
  get: function (target, key) {
    return key in target
      ? target[key]
      : `Property ${key} doesn't exist in this object`;
  },
};

const proxy = new Proxy(person, handler);
console.log(proxy.name); // Peter
console.log(proxy.lastName); // Property lastName doesn't exist in this object
Enter fullscreen mode Exit fullscreen mode

There is a lot of things you can do using proxy. I'm going to show you a useful case.

Cache
const getArticles = (person) => {
  fetch("api-url").then((articles) => {
    // do something with articles
  });
};
Enter fullscreen mode Exit fullscreen mode

This would mean that whenever the articles of a person is required, a new call has to be made. Instead, you could cache the articles when it is first requested, and subsequent requests could be taken from the cache.

const cache = {
  Peter: ["Article 1", "Article 2"],
};

const handler = {
  get: function (target, person) {
    if (target[person]) {
      return target[person];
    } else {
      // fetch here
      fetch("api-url").then((articles) => {
        target[person] = articles;
        return articles;
      });
    }
  },
};

const proxy = new Proxy(cache, handler);
Enter fullscreen mode Exit fullscreen mode

This way, fetch will be executed only if the person isn't in cache object.

You can do many things with proxy like validation, providing a read-only view of an object, private properties, etc.

Composition

Composition is a simple but powerful concept. It's just a simple way to use multiple functions. Each function receives an input and hands its output to the next function.

Perhaps you have used the composition without knowing what that conceptually means. I will show you a simple example.

Let's imagine that we want to clean up the input that a user entered, and we have a function that removes white spaces, and another function that removes special characters.

const withoutSpaces = (value) => value.replace(/ /g, "");
const removeSpecialChars = (value) => value.replace(/[^a-zA-Z ]/g, "");
Enter fullscreen mode Exit fullscreen mode

We can compose these functions, into one, by doing this:

const compose = (f1, f2) => (value) => f2(f1(value));
const emptyInput = compose(withoutSpaces, removeSpecialChars);
console.log(emptyInput("  d'ani   el")); // daniel
Enter fullscreen mode Exit fullscreen mode

Our compose function returns a new function, which receives a parameter and returns the clean string. If you pay attention to this f2 (f1 (value)), you will notice that what we do is pass the result of the first function to the second function, it is that simple.

If we want to compose more than one function, we can take advantage of the reduceRight method.

const withoutSpaces = (value) => value.replace(/ /g, "");
const removeSpecialChars = (value) => value.replace(/[^a-zA-Z ]/g, "");
const toLowerCase = (value) => value.toLowerCase();
const compose = (...fns) => (initialVal) =>
  fns.reduceRight((val, fn) => fn(val), initialVal);

const emptyInput = compose(withoutSpaces, removeSpecialChars, toLowerCase);
console.log(emptyInput("  d'AVID   ")); // david
Enter fullscreen mode Exit fullscreen mode

Conclusion

I hope these concepts have allowed you to learn / understand something new, if you have something to add, put it in comments section.

I will be writing in the next few days about other interesting concepts in JavaScript.

Top comments (2)

Collapse
 
beyarz profile image
Beyar

This was gold

Collapse
 
rbauhn profile image
Robin Bauhn • Edited

Nice article, although I would suggest changing compose to:

const compose = (...funcs) => value => {
    let output = value
    for (func of  funcs){
        output = func(output)
    }
    return output
}
Enter fullscreen mode Exit fullscreen mode