Translator: Front-end wisdom

Original text: tylermcginnis.com/beginners-g…


Click “like” and then look, wechat search [Big Move the world] pay attention to this person without dACHang background, but with a positive attitude upward. In this paper, making github.com/qq449245884… Has been included, the article has been categorized, also organized a lot of my documentation, and tutorial materials.

Everyone said there was no project on your resume, so I found one and gave it away【 Construction tutorial 】.

Without learning how to handle objects, you won’t get very far in JavaScript. They are the foundation of almost every aspect of the JavaScript programming language. In fact, learning how to create objects is probably one of the first things you learn.

Objects are key/value pairs. The most common way to create an object is to use curly braces {} and dot notation to add properties and methods to the object.

let animal = {}
animal.name = 'Leo'
animal.energy = 10

animal.eat = function (amount) {
  console.log(`${this.name} is eating.`)
  this.energy += amount
}

animal.sleep = function (length) {
  console.log(`${this.name} is sleeping.`)
  this.energy += length
}

animal.play = function (length) {
  console.log(`${this.name} is playing.`)
  this.energy -= length
}
Copy the code

Now, in our application, we need to create multiple Animals. The next step, of course, is to encapsulate the logic. When we need to create a new animal, we just call the function unctional Instantiation. We call the function itself “constructor” because it is responsible for “constructing” a new object.

Instantiation of a function

function Animal (name, energy) {
  let animal = {}
  animal.name = name
  animal.energy = energy

  animal.eat = function (amount) {
    console.log(`${this.name} is eating.`)
    this.energy += amount
  }

  animal.sleep = function (length) {
    console.log(`${this.name} is sleeping.`)
    this.energy += length
  }

  animal.play = function (length) {
    console.log(`${this.name} is playing.`)
    this.energy -= length
  }

  return animal
}

const leo = Animal('Leo', 7)
const snoop = Animal('Snoop', 10)
Copy the code

Now, whenever we want to create a new Animal (or, more broadly, a new “instance”), all we have to do is call our Animal function and pass in the parameters: name and energy. It’s very useful and very simple. But what are the downsides of this model?

The biggest problem that we’re trying to solve has to do with the three methods in the function – eat, sleep and play. Each of these methods is not only dynamic, but they are also completely generic. This means that there is no reason to recreate these methods when creating new animals, as we do now. We’re just wasting memory by making every new object bigger than it really needs to be.

Can you think of a solution? What if instead of recreating these methods every time we create a new animal, we move them to our own object and then we can have each animal reference that object? We can call this pattern function instantiation and shared methods.

Function instantiation and shared methods

const animalMethods = {
  eat(amount) {
    console.log(`${this.name} is eating.`)
    this.energy += amount
  },
  sleep(length) {
    console.log(`${this.name} is sleeping.`)
    this.energy += length
  },
  play(length) {
    console.log(`${this.name} is playing.`)
    this.energy -= length
  }
}

function Animal (name, energy) {
  let animal = {}
  animal.name = name
  animal.energy = energy
  animal.eat = animalMethods.eat
  animal.sleep = animalMethods.sleep
  animal.play = animalMethods.play

  return animal
}

const leo = Animal('Leo', 7)
const snoop = Animal('Snoop', 10)
Copy the code

By moving the shared methods to their own object and referencing that object in the Animal function, we have now solved the memory waste and new object size problems.

Object.create

Let’s use object.create again to improve our example. Simply put, Object.create allows you to create an Object that will be delegated to another Object in the event of a failed lookup. In other words, object.create allows you to create an Object that, whenever a property lookup on that Object fails, can query another Object to see if that other Object has that property. Let’s look at some code:

const parent = {
  name: 'Stacey',
  age: 35,
  heritage: 'Irish'
}

const child = Object.create(parent)
child.name = 'Ryan'
child.age = 7

console.log(child.name) // Ryan
console.log(child.age) // 7
console.log(child.heritage) // Irish
Copy the code

So, in the example above, because the child was created with object.create(parent), JavaScript delegates the lookup to the parent object whenever the property lookup on the Child object fails. This means that even though Child has no heritage, when you print child.heritage, it will find the corresponding heritage from the parent object and print it out.

How can I now use Object.create to simplify the previous Animal code? Well, instead of adding all shared methods to Animal one by one, we can delegate to animalMethods using Object.create. For b-grid point, this is called instantiation using shared methods and object.create functions.

Instantiate using shared methods and object.create functions

const animalMethods = {
  eat(amount) {
    console.log(`${this.name} is eating.`)
    this.energy += amount
  },
  sleep(length) {
    console.log(`${this.name} is sleeping.`)
    this.energy += length
  },
  play(length) {
    console.log(`${this.name} is playing.`)
    this.energy -= length
  }
}

function Animal (name, energy) {
  let animal = Object.create(animalMethods)
  animal.name = name
  animal.energy = energy

  return animal
}

const leo = Animal('Leo', 7)
const snoop = Animal('Snoop', 10)

leo.eat(10)
snoop.play(5)
Copy the code

So now when we call Leo.eat, JavaScript will look for the eat method on the Leo Object. Since there is no eat method in Leo, the search will fail, and because of object.create, it will delegate to the animalMethods Object, So the eat method is found on the animalMethods object.

So far so good. Still, some improvements can be made. In order to share methods across instances, having to manage a single object (animalMethods) seems silly. We want this to be a common feature implemented in the language itself, so we need to introduce the next property – Prototype.

So what exactly is Prototype in JavaScript? Well, briefly, every function in JavaScript has a Prototype property that references an object.

function doThing () {}
console.log(doThing.prototype) // {}
Copy the code

What if instead of creating a separate object to manage our methods (animalMethods in the example above), we just put each method on the prototype of the Animal function? Then all we need to do is instead of delegating object. create to animalMethods, we can use it to delegate animal.Prototype. We call this pattern stereotype instantiation.

Prototype instantiation

function Animal (name, energy) {
  let animal = Object.create(Animal.prototype)
  animal.name = name
  animal.energy = energy

  return animal
}

Animal.prototype.eat = function (amount) {
  console.log(`${this.name} is eating.`)
  this.energy += amount
}

Animal.prototype.sleep = function (length) {
  console.log(`${this.name} is sleeping.`)
  this.energy += length
}

Animal.prototype.play = function (length) {
  console.log(`${this.name} is playing.`)
  this.energy -= length
}

const leo = Animal('Leo', 7)
const snoop = Animal('Snoop', 10)

leo.eat(10)
snoop.play(5)
Copy the code

Again, Prototype is just a property that every function in JavaScript has, which, as we saw earlier, allows us to share methods across all instances of a function. All of our functions are still the same, but now we don’t have to manage a single object for all of our methods, we just need to use another object, Animal.prototype, built into the Animal function itself.

further

Now we know three points:

  1. How to create a constructor.

  2. How to add methods to a constructor prototype.

  3. How to delegate a failed lookup to a function’s prototype using Object.create.

These three points are fundamental to any programming language. Is JavaScript really so bad that there isn’t an easier way to do the same thing? As you might have guessed, there is now, and it is implemented by using the new keyword.

Reviewing our Animal constructor, the two most important parts are creating the object and returning it. If we do not create objects using Object.create, we will not be able to delegate the prototype of a function on a failed lookup. Without the return statement, we would never return the object we created.

function Animal (name, energy) {
  let animal = Object.create(Animal.prototype) // 1 
  animal.name = name
  animal.energy = energy

  return animal   // 2
}
Copy the code

One cool thing about new is that when you call a function using the new keyword, the following two lines numbered 1 and 2 will implicitly (underneath) do it for you, creating an object called this.

Using comments to show what’s going on underneath, and assuming the Animal constructor is called with the new keyword, you can rewrite it like this.

function Animal (name, energy) {
  // const this = Object.create(Animal.prototype)

  this.name = name
  this.energy = energy

  // return this
}

const leo = new Animal('Leo', 7)
const snoop = new Animal('Snoop', 10)
Copy the code

Normal as follows:

function Animal (name, energy) {
  this.name = name
  this.energy = energy
}

Animal.prototype.eat = function (amount) {
  console.log(`${this.name} is eating.`)
  this.energy += amount
}

Animal.prototype.sleep = function (length) {
  console.log(`${this.name} is sleeping.`)
  this.energy += length
}

Animal.prototype.play = function (length) {
  console.log(`${this.name} is playing.`)
  this.energy -= length
}

const leo = new Animal('Leo', 7)
const snoop = new Animal('Snoop', 10)
Copy the code

Again, the reason we did this, and the object was created for us, is because we called the constructor with the new keyword. If you omit new when calling a function, the object is never created and is not returned implicitly. We can see this problem in the following example.

function Animal (name, energy) {
  this.name = name
  this.energy = energy
}

const leo = Animal('Leo', 7)
console.log(leo) // undefined
Copy the code

This pattern is called pseudo-class instantiation.

For those unfamiliar, classes allow you to create blueprints for objects. Then, whenever you create an instance of the class, you can access the properties and methods defined in that object.

Sound familiar? That’s basically what we did with the Animal constructor above. Instead of using the class keyword, however, we recreated the same functionality using just regular old JavaScript functions. Sure, it takes a little extra work and some understanding of what’s going on “underneath” in JavaScript, but the result is the same.

That’s good news. JavaScript is not a dead language. The TC-39 Committee is continuously improved and supplemented. This means that even if the initial version of JavaScript does not support classes, there is no reason to add them to the official specification. In fact, that is exactly what the TC-39 Commission did. In 2015, EcmaScript (the official JavaScript specification) 6 was released, supporting the class and class keywords. Let’s see how the Animal constructor above uses the new class syntax.

class Animal {
  constructor(name, energy) {
    this.name = name
    this.energy = energy
  }
  eat(amount) {
    console.log(`${this.name} is eating.`)
    this.energy += amount
  }
  sleep(length) {
    console.log(`${this.name} is sleeping.`)
    this.energy += length
  }
  play(length) {
    console.log(`${this.name} is playing.`)
    this.energy -= length
  }
}

const leo = new Animal('Leo', 7)
const snoop = new Animal('Snoop', 10)
Copy the code

This is relatively straightforward compared to the previous example.

So if this is the new way to create classes, why do we spend so much time reviewing the old way? The reason is that the new approach (using the class keyword) is mostly just “syntactic sugar” from the existing approach of what we call pseudo-class instantiation patterns. To fully understand the convenient syntax of ES6 classes, you must first understand the pseudo-class instantiation pattern.

So far, we’ve covered the basics of JavaScript prototyping. The rest of this article will be devoted to understanding other good topics related to it. In another article, we’ll look at how to take advantage of these fundamentals and use them to understand how inheritance works in JavaScript.

Array methods

We discussed in depth above how to share methods between instances of a class, and you should place these methods on class (or function) prototypes. If we look at the Array class, we can see the same pattern.

onst friends = []
Copy the code

Is a syntactic sugar instead of using new Array().

const friendsWithSugar = []

const friendsWithoutSugar = new Array()
Copy the code

One thing you probably never thought about is how each instance of an array has all the built-in methods (splice, slice, POP, etc.)?

As you now know, this is because these methods exist on Array.prototype, and when you create a new Array instance, you use the new keyword to set the delegate to Array.prototype in a failed lookup.

We can print array. prototype to see which methods are available:

Manufacturer: ƒn concat() Constructor: ƒn Array() copyWithin: ƒn copyWithin() entries: ƒn entries() every: ƒn every() fill: ƒn fill() filter: ƒn filter() find: ƒn find() findIndex: ƒn findIndex() ƒn forEach() includes: ƒn includes() join: ƒn join() keys: ƒ N lastIndexOf() Length: 0 map: ƒ N map() POP: ƒn POP () push: ƒn push() reduce: ƒn Reduce () reduceRight: ƒ N reduceRight() : ƒn reverse() shift: ƒn shift() slice: ƒn slice() some: ƒn some() sort: ƒn sort() ƒ N splice() toLocaleString: ƒ N toLocaleString() toString: ƒn toString() unshift() VALUES: ƒn values() */Copy the code

The exact same logic exists for objects. All objects will be delegated to Object.prototype after a failed lookup, which is why all objects have methods like toString and hasOwnProperty

A static method

So far, we’ve discussed why and how methods are shared between instances of classes. But what if we have a method that is important to the class, but we don’t need to share that method between instances? For example, what if we had a function that took a list of Animal instances and determined which one to feed next? Our method is called nextToEat.

function nextToEat (animals) {
  const sortedByLeastEnergy = animals.sort((a,b) => {
    return a.energy - b.energy
  })

  return sortedByLeastEnergy[0].name
}
Copy the code

Since we don’t want nextToEat to be shared between all instances, it doesn’t make sense to use nextToEat on animal.prototype. Instead, we can think of it as a helper.

So if nextToEat shouldn’t be in animal.prototype, where should we put it? The obvious answer is that we could put nextToEat in the same scope as our Animal class and then reference it as we usually do.

class Animal {
  constructor(name, energy) {
    this.name = name
    this.energy = energy
  }
  eat(amount) {
    console.log(`${this.name} is eating.`)
    this.energy += amount
  }
  sleep(length) {
    console.log(`${this.name} is sleeping.`)
    this.energy += length
  }
  play(length) {
    console.log(`${this.name} is playing.`)
    this.energy -= length
  }
}

function nextToEat (animals) {
  const sortedByLeastEnergy = animals.sort((a,b) => {
    return a.energy - b.energy
  })

  return sortedByLeastEnergy[0].name
}

const leo = new Animal('Leo', 7)
const snoop = new Animal('Snoop', 10)

console.log(nextToEat([leo, snoop])) // Leo
Copy the code

It works, but there’s a better way.

As long as you have a method that is specific to the class itself but does not need to be shared between instances of the class, you can define it as a static property of the class.

class Animal {
  constructor(name, energy) {
    this.name = name
    this.energy = energy
  }
  eat(amount) {
    console.log(`${this.name} is eating.`)
    this.energy += amount
  }
  sleep(length) {
    console.log(`${this.name} is sleeping.`)
    this.energy += length
  }
  play(length) {
    console.log(`${this.name} is playing.`)
    this.energy -= length
  }
  static nextToEat(animals) {
    const sortedByLeastEnergy = animals.sort((a,b) => {
      return a.energy - b.energy
    })

    return sortedByLeastEnergy[0].name
  }
}
Copy the code

Now, because we added nextToEat as a static property on the class, it exists on the Animal class itself (not its prototype) and can be called with animal.nexttoeat.

const leo = new Animal('Leo', 7)
const snoop = new Animal('Snoop', 10)

console.log(Animal.nextToEat([leo, snoop])) // Leo
Copy the code

Since we’ve all followed a similar pattern in this article, let’s look at how you can accomplish the same thing with ES5. In the example above, we saw how to use the static keyword to place methods directly on the class itself. With ES5, the same pattern is as simple as manually adding methods to function objects.

function Animal (name, energy) {
  this.name = name
  this.energy = energy
}

Animal.prototype.eat = function (amount) {
  console.log(`${this.name} is eating.`)
  this.energy += amount
}

Animal.prototype.sleep = function (length) {
  console.log(`${this.name} is sleeping.`)
  this.energy += length
}

Animal.prototype.play = function (length) {
  console.log(`${this.name} is playing.`)
  this.energy -= length
}

Animal.nextToEat = function (nextToEat) {
  const sortedByLeastEnergy = animals.sort((a,b) => {
    return a.energy - b.energy
  })

  return sortedByLeastEnergy[0].name
}

const leo = new Animal('Leo', 7)
const snoop = new Animal('Snoop', 10)

console.log(Animal.nextToEat([leo, snoop])) // Leo
Copy the code

Gets the prototype of the object

Regardless of which mode you use to create an Object, you can use the Object.getProtoTypeof method to complete retrieving the prototype of that Object.

function Animal (name, energy) { this.name = name this.energy = energy } Animal.prototype.eat = function (amount) { console.log(`${this.name} is eating.`) this.energy += amount } Animal.prototype.sleep = function (length) { console.log(`${this.name} is sleeping.`) this.energy += length } Animal.prototype.play = function (length) { console.log(`${this.name} is playing.`) this.energy -= length } const leo = new Animal('Leo', 7) const proto = object.getprototypeof (Leo) console.log(proto) // {constructor: ƒ, eat: ƒ, sleep: ƒ, play: ƒ} proto === Animal. Prototype // trueCopy the code

The above code has two important points.

First, you’ll notice that Proto is an object with four methods, constructor, Eat, sleep, and Play. It makes sense. We pass the instance using getPrototypeOf, Leo gets the prototype of the instance, and that’s where all our methods are.

This also tells us another thing about Prototype that we haven’t discussed yet. By default, the Prototype object will have a constructor attribute that points to the initial function or the class that created the instance. This also means that because JavaScript places constructor attributes on stereotypes by default, any instance can pass.

Object.getprototypeof (Leo) === animal.prototype And that makes sense. The Animal constructor has a Prototype property that allows us to share methods between all instances, and getPrototypeOf allows us to see the prototype of the instance itself.

function Animal (name, energy) {
  this.name = name
  this.energy = energy
}

const leo = new Animal('Leo', 7)
console.log(leo.constructor) // Logs the constructor function
Copy the code

In keeping with what we discussed earlier with Object.create, this works because any Animal instance will be delegated to Animal.Prototype on a failed lookup. Thus, when you try to access Leo. constructor, Leo has no constructor property, so it delegates the lookup to animal. prototype, which does have a constructor property.

You’ve probably seen prototypes before that use __proto__ to get instances, a relic of the past. Instead, use Object.getPrototypeof (instance) as described above

Determines whether an attribute is contained on the stereotype

In some cases, you need to know whether the property exists on the instance itself or on the prototype of the object delegate. We can see this by looping through the Leo objects we created. Use the for in loop as follows:

function Animal (name, energy) {
  this.name = name
  this.energy = energy
}

Animal.prototype.eat = function (amount) {
  console.log(`${this.name} is eating.`)
  this.energy += amount
}

Animal.prototype.sleep = function (length) {
  console.log(`${this.name} is sleeping.`)
  this.energy += length
}

Animal.prototype.play = function (length) {
  console.log(`${this.name} is playing.`)
  this.energy -= length
}

const leo = new Animal('Leo', 7)

for(let key in leo) {
  console.log(`Key: ${key}. Value: ${leo[key]}`)
}
Copy the code

My expected print might look like this:

Key: name. Value: Leo
Key: energy. Value: 7
Copy the code

However, if you run the code, you see something like this –

Key: name. Value: Leo
Key: energy. Value: 7
Key: eat. Value: function (amount) {
  console.log(`${this.name} is eating.`)
  this.energy += amount
}
Key: sleep. Value: function (length) {
  console.log(`${this.name} is sleeping.`)
  this.energy += length
}
Key: play. Value: function (length) {
  console.log(`${this.name} is playing.`)
  this.energy -= length
}
Copy the code

Why is that? For the for in loop, the loop iterates through all the enumerable properties of the object itself and the stereotype it delegates to. Because by default, any property you add to a function prototype is enumerable, we see not only name and energy, but all the methods on the prototype -eat, sleep, play.

To solve this problem, we need to specify that all stereotype methods are not enumerable, or that only the stereotype whose properties are in the Leo object itself is printed instead of the stereotype that Leo delegates to the failed lookup. This is where hasOwnProperty can help us.

. const leo = new Animal('Leo', 7) for(let key in leo) { if (leo.hasOwnProperty(key)) { console.log(`Key: ${key}. Value: ${leo[key]}`) } }Copy the code

Now we see only the properties of the Leo object itself, not the prototype of the Leo delegate.

Key: name. Value: Leo
Key: energy. Value: 7
Copy the code

If you’re still confused about hasOwnProperty, here’s some code to help you figure it out.

function Animal (name, energy) {
  this.name = name
  this.energy = energy
}

Animal.prototype.eat = function (amount) {
  console.log(`${this.name} is eating.`)
  this.energy += amount
}

Animal.prototype.sleep = function (length) {
  console.log(`${this.name} is sleeping.`)
  this.energy += length
}

Animal.prototype.play = function (length) {
  console.log(`${this.name} is playing.`)
  this.energy -= length
}

const leo = new Animal('Leo', 7)

leo.hasOwnProperty('name') // true
leo.hasOwnProperty('energy') // true
leo.hasOwnProperty('eat') // false
leo.hasOwnProperty('sleep') // false
leo.hasOwnProperty('play') // false
Copy the code

Checks whether the object is an instance of a class

Sometimes you want to know if an object is an instance of a particular class. To do this, you can use the instanceof operator. The use case is pretty simple, but the actual syntax is a little weird if you’ve never seen it before. Here’s how it works

object instanceof Class
Copy the code

The above statement returns true if Object is an instance of Class, false otherwise. Going back to our Animal example, we have something similar:

function Animal (name, energy) {
  this.name = name
  this.energy = energy
}

function User () {}

const leo = new Animal('Leo', 7)

leo instanceof Animal // true
leo instanceof User // false
Copy the code

Instanceof works by checking for the presence of constructive.Prototype in the object prototype chain. In the above example, Leo instanceof Animal is true because Object.getProtoTypeof (Leo) == animal.prototype. In addition, Leo instanceof User is false because Object.getPrototypeof (Leo)! = = User. The prototype.

Create a new unknowable constructor

Can you spot the error in the following code

function Animal (name, energy) {
  this.name = name
  this.energy = energy
}

const leo = Animal('Leo', 7)
Copy the code

Even experienced JavaScript developers sometimes stumble over the examples above. Since we are using the pseudo-class instance pattern we learned earlier, we need to be sure to call the Animal constructor using the new keyword. If we do not, the this keyword will not be created and it will not be returned implicitly.

As a refresher, the commented out lines are what happens behind the scenes when the new keyword is used on a function.

function Animal (name, energy) {
  // const this = Object.create(Animal.prototype)

  this.name = name
  this.energy = energy

  // return this
}
Copy the code

This seems like a very important detail for other developers to remember. Assuming we’re working with other developers, is there a way we can make sure we always call our Animal constructor using the new keyword? It turns out you can do this by using the Instanceof operator we learned earlier.

If a constructor is called using the new keyword, then this inside the constructor body will be an instance of the constructor itself.

function Aniam (name, energy) { if (this instanceof Animal === false) { console.warn('Forgot to call Animal with the new keyword') } this.name  = name this.energy = energy }Copy the code

Now, what if we call the function again, but this time with the new keyword, instead of just printing a warning to the caller of the function?

function Animal (name, energy) {
  if (this instanceof Animal === false) {
    return new Animal(name, energy)
  }

  this.name = name
  this.energy = energy
}
Copy the code

Now, it still works with or without the new keyword calling Animal.

Rewrite the Object. The create

In this article, we rely heavily on object.create to create objects that delegate to the constructor prototype. At this point, you should know how to use Object.create in your code, but one thing you might not have thought about is how Object.create actually works. To give you a real idea of how Object.create works, we’ll recreate it ourselves. First, what do we know about how Object.Create works?

  1. It takes arguments to an object.

  2. It creates an object that delegates to the parameter object if the lookup fails

  3. It returns the newly created object.

    Object.create = function (objToDelegateTo) {

    }

Now we need to create an object that will delegate to the parameter object in the failed lookup. This is a little tricky. To do this, we will use knowledge about the new keyword.

First, create an empty function inside the Object.create body. Then, set the null function’s prototype to equal the passed parameter object. Then, return to call our empty function using the new keyword.

Object.create = function (objToDelegateTo) {
  function Fn(){}
  Fn.prototype = objToDelegateTo
  return new Fn()
}
Copy the code

When we create a new function Fn in the above code, it takes a Prototype property. When we call it using the new keyword, we know that we will get an object that will be delegated to the function prototype in the failed lookup.

If we override the function’s prototype, then we can decide which object to delegate in a failed lookup. So in the above example, we override Fn’s prototype with the Object passed in when calling Object.Create, which we call objToDelegateTo.

Note that we only support a single parameter to Object.create. The official implementation also supports a second optional parameter that allows you to add more properties to the object you create.

Arrow function

Arrow functions do not have their own this keyword. Therefore, the arrow function cannot be a constructor, and if you try to call the arrow function using the new keyword, it will raise an error.

const Animal = () => {}

const leo = new Animal() // Error: Animal is not a constructor
Copy the code

Also, because we explained above that the pseudo-class instance pattern cannot be used with arrow functions, arrow functions also have no stereotype attributes.

const Animal = () => {}
console.log(Animal.prototype) // undefined
Copy the code

Your likes are my motivation to keep sharing good things.

communication

This article is updated every week, you can search wechat “big move the world” for the first time to read and urge more (one or two earlier than the blog hey), this article GitHub github.com/qq449245884… It has been included and sorted out a lot of my documents. Welcome Star and perfect. You can refer to the examination points for review in the interview.