Nowadays, all kinds of frameworks and tools “run amok”, everywhere in the principle and source code, more cross-end technology needs us to explore, but if the basic skills are not good, learn what is half the effort, the effect is very bad, take time at the same time to blow confidence. This article, for my plan [light talk front end] series (seven), aims to systematically and logically share the knowledge of native JavaScript with you, to help you more easily geographical clear knowledge system, better understanding and memory, I do my best, hope to live up to expectations.
“Everything” is an object. What does that object have and what can it do?
Some of the most enduring topics, such as prototype chains, this, and deep/shallow copies, are all about objects, and the story of objects is much more than that, so let’s talk about it appropriately.
What is the object
It is a collection of properties – “what” and methods – “what”.
There are two usage scenarios:
At the language level, JavaScript runs on an object system, in which each data type is simultaneously a class of objects with its own attributes and methods for programming.
let str = "hello world"; STR. Length / / 11 STR. Substr (0, 5) / / "hello"Copy the code
Business hierarchy: Depending on the business type, multiple roles can be abstracted from specific requirements. Each role can be encapsulated as an object with its own attributes and methods.
Let teacher = {name:" lily ", age:30, work:" teacher "}Copy the code
Create an object
Objects need to be created in order to be used, and there are many ways to create objects, so let’s go through them one by one, and talk about the past and present.
- In this respect
When we talk about types, we have seen the original Object — Object. We use the new operator to create an object and add attributes and methods to it:
let person = new Object(); person.name = 'idea'; person.age = 18; Person. Work = 'brick '; Person.run = function(){console.log(" I can run ")}Copy the code
When you have objects, you can use them when you need them. For example, the data that the front end requests from the back end is usually in the form of objects, and you can take its attribute values and display them on the page. Of course, the front end can create objects to store individuals in some sense, for transfer or other operations.
This is not a neat way to create objects. Instead, we write a lot of repeated object names. A more intuitive and common method is called “object literals” :
Let person = {name :'idea', age :18, work :' move ', run(){console.log(" I can run ")}}Copy the code
Simplicity of writing is also important to the engineer’s development experience, so it is common in development.
Viewed this way, an object is a container and not complicated, but both approaches have a drawback: creating multiple objects with the same interface requires a lot of repetitive code. In other words, it can only be created one by one, without encapsulation, reuse, and inheritance.
Better creation and inheritance
- The factory pattern
When it comes to packaging and reuse, functions come naturally. If you can encapsulate the entire object creation process in a single function, like a small object “factory,” that solves the above problem. This is where the “factory pattern” comes from.
function createPerson(name,age,work){ let obj = new Object(); obj.name = name; obj.age = age; obj.work = work; Obj. Run = function(){console.log(this.name + 'run ')} return obj} let person1 = createPerson(" 三",16," 三") let person2 = createPerson(" createPerson ",18," product manager ")Copy the code
This solution solves the repetitive writing problem of object creation, which is a good solution, but it is a bit incomplete in that it encapsulates the behavior “only” and creates objects that have no relationship to the source, i.e. person1, Person2, and createPerson.
Many times it is important to establish connections between them, such as inheriting properties and methods, so you can continue to improve.
- The constructor
What is a constructor – a function used to construct an instance of an object.
Constructors in ECMAScript are used to create objects of a specific type, like new String() to create strings and new Number() to create numbers.
String() and Number() are built-in functions, and here we are talking about custom constructors.
It is written in a similar way to the factory mode, but is fundamentally different. For example, we can write the above object like this:
function Person(name,age,work){ this.name = name; this.age = age; this.work = work; This. Run = function(){console.log(this.name + 'I ')}} let idea = new Person(' I ',18,' I '); Idea.name //' inspiration 'idea.age //18Copy the code
It has the following characteristics:
- Capital letters are easier to distinguish from ordinary functions
- Instead of explicitly creating an object, properties and methods are assigned directly to this
- There is no return
So what’s the essential difference? This is to understand the process of construction, as follows:
Create a new object in memory.
(2) The [[Prototype]] property inside this new object is assigned to the constructor’s Prototype property.
(3) This inside the constructor is assigned to the new object.
(4) Execute internal code to add attributes to the new object.
(5) If the constructor returns a non-empty object, return that object; Otherwise, the newly created object is returned.
As you can see, not only does it look like the factory pattern, it does what it does behind the scenes, creating a new object, adding properties and methods, and then returning that object. One difference is that in step 2, the [[Prototype]] property of the new object is assigned to the constructor’s Prototype property, which establishes a link between the instance and the constructor.
idea.constructor == Person //true
Copy the code
Therefore, using the constructor pattern to create object instances can not only encapsulate and reuse, but also establish a relationship between the instance and the constructor.
However, this does not mean that constructors are perfect. We find that constructors have methods in them. Methods are functions and functions are objects, which is equivalent to creating a new Function instance every time we create an instance.
Let lisi = new Person(" lisi ",18," product manager ") lisi.run == idea.run //falseCopy the code
The two instances are independent of each other, but they’re actually doing the same thing, which is a waste of resources.
How to do? One way to do this is to put the function definition outside of the constructor, so what happens when you’re new, I put you outside of the new operation, don’t I?
It is feasible in effect, but it has several disadvantages:
- Contamination global scope
- If you need more than one method, you define more than one method globally, which gets messy
- These methods are related to a single object, not to anything else, and the attribution is not clear
To sum up, this scheme is not worth the cost, so it is not recommended to adopt it. Is there a better way?
Not so fast, let’s look at the other one first.
The prototype pattern
“Each function creates a Prototype attribute.” Knowing this is a prerequisite to understanding archetypal patterns.
Prototype is an object that contains properties and methods shared by instances created with a specific reference type.
In effect, this object is a prototype of the object created by calling the constructor. Therefore, properties and methods that can be defined on it are shared by object instances. Look at the code:
function Person(){} person.prototype.name = 'idea'; person.prototype.age = '18'; Person. Prototype. Work = 'programmer '; person.prototype.run = function(){ console.log(this.name); };Copy the code
So, when we want to create an object, we can:
let person1 = new Person();
let person2 = new Person();
person1.run == person2.run //true
Copy the code
The method run gets shared.
Let’s talk a little bit about how this effect works.
As mentioned earlier, whenever a function is created, a Prototype attribute is created for that function according to certain rules. By default, all stereotype objects automatically get a property called constructor that refers back to the constructor associated with them. For the previous example, a Person. The prototype. The constructor to the Person. Additional properties and methods may then be added to the prototype object.
Each time a new instance is created, the instance’s internal [[Prototype]] pointer is assigned to the constructor’s Prototype object. It should be noted that there is no standard way to access the [[Prototype]] feature, but most browsers will expose the __proto__ property on each object through which the object’s Prototype can be accessed. The instance is linked to the prototype object by __proto__, different instances of the same constructor share the prototype object by __proto__, and Person.prototype points to the prototype object, so properties and methods defined on the prototype are shared.
On top of that, the Object type has a setPrototypeOf() method that writes new values to the instance’s [[Prototype]]. This allows you to override the stereotype inheritance of an object.
let person = {
age : 18
}
let person1 = {
name:'idea'
}
Object.setPrototypeOf(person1,person)
person1 // {name: "idea"}
person1.age //18
Copy the code
This method takes a target object and a prototype object and associates them so that the target object inherits the properties of the prototype object.
However, modifying inheritance is a risky (or performance-destroying) operation because you don’t know how many objects will be associated with it. If you do need to specify a prototype for an Object instance when it is created, there is an alternative — Object.create().
The object.create () method creates a new Object, using an existing Object to provide the __proto__ of the newly created Object.
let person = {
age : 18
}
let person1 = Object.create(person);
person1.age //18
Copy the code
From this, you can also learn something about an empty object called “clean.” We create empty objects using these methods:
let obj = {}
let obj = new Object()
let obj = Object.Create(null)
Copy the code
If you are not completely new to objects, you will know that objects created in the first two ways have properties and methods that inherit from Object, even though they appear to have no added properties and methods. The third way, which has no properties and no methods, is called a “clean” empty Object.
These are some of the little things that have evolved from our discussion of “prototype patterns,” but the focus is still on the patterns themselves.
“Prototyping” looks great, but it’s not without its problems. First, it weakens the ability to pass arguments to constructors, and all instances default to the same attribute values, which is obviously inconvenient, but not the biggest problem with prototypes. The biggest problem stems from its shared nature, which is that “success is nothing, failure is nothing”. Sharing works well for functions and accepts raw value attributes. If reference values are involved, the problem is exposed.
function Person(){} Person.prototype = { bag:["thinkPad","iphone12","ipad","mac"] } let person1 = new Person(); let person2 = new Person(); person1.bag // ["thinkPad", "iphone12", "ipad", "mac"] person2.bag // ["thinkPad", "iphone12", "ipad", Pop () person1.bag // ["thinkPad", "iphone12", "iPad "] person2.bag // ["thinkPad", "iphone12", "ipad"]Copy the code
The person1 and person2 knapsacks point to the same address. If you change one of the knapsacks, the other will change as well, but this is personalization. After all, not everyone can use a MAC.
Because of this drawback, the archetypal pattern is not usually used alone, and the following pattern exists.
Combinatorial inheritance pattern
Constructor members are private, and prototype members are shared. Some members need to be shared, and some need to be private. Why not combine the two?
We can change the above code to something like this:
function Person(name,age,work,bag){ this.name = name; this.age = age; this.work = work; this.bag = bag; } person.prototype. run = function(){console.log(this.name + 'will run ')} let person1 = new Person("idea",18," programmer ",["thinkPad"]); Let person2 = new Person("lili",16," Product Manager ",[" MAC "]) person1.run() // idea will run person2.run() // lili will run Person1.bag. push(" MAC ") person1.bag // ["thinkPad", "MAC "] person2.bag // [" MAC "]Copy the code
This approach circumvented the shortcomings of constructors and archetypal patterns, while taking advantage of the strengths of each, and became popular.
But after ES6, there are better alternatives.
Class
In orthodox object-oriented languages, there is the concept of a class. A class is a template that describes the behavior and state of a class of objects. Even with classes, you can create many similar objects.
There were no classes in JavaScript prior to ES6, and they had to be simulated by other methods, such as constructors described earlier, or combinatorial inheritance. ES6 has introduced a new tool for creating classes, Class. Let’s start with the class definition:
class Person{}
const Person = class {}
Copy the code
You can either define it directly or use an assignment expression, which seems like a completely new approach, but you’ve probably seen it in a lot of places where class is just a syntactic sugar. What is grammar sugar? The difference in grammar makes it more comfortable and natural to use. Behind it is the combination of constructors and stereotypes.
Classes can contain constructor methods, instance methods, getters, setter functions, and static class methods, but they are not required. As in the example above, an empty class definition works just as well. By default, the code in a class executes in strict mode.
Let’s define a complete class to recognize it.
class Person{ constructor(name,age,work){ this.name = name; this.age = age; this.work = work } get getName(){ return this.name } set setAge(newAge){ this.age = newAge; Static sayName(){say.log ("say"+ this.log)}} run(){console.log("say"+ this.log)}Copy the code
You can then define an instance like this:
Let person1 = new Person("idea",18," programmer ")Copy the code
Defining classes makes it very easy to define members that should exist on instances, members that should exist on stereotypes, and members that should exist on the class itself.
Instance members
The class constructor is executed each time a class is called through new. Inside functions, you can add “own” attributes to newly created instances. That’s what the Person class constructor above does.
Each instance corresponds to a unique member object, which means that none of the members are shared on the stereotype.
Prototype methods and accessors
To share methods between instances, the class definition syntax treats methods defined in a class block as prototype methods. For example, the Run method in Person.
Class definitions also support getting and setting accessors. The syntax and behavior are the same as normal objects. Like getName and setAge above. They are called when instance properties are accessed or modified, respectively.
Static class
Use the static keyword as prefix. In static members, this refers to the class itself. Typically used to perform operations that are not instance-specific and do not require the existence of an instance of a class.
For example, the sayName method in the code above:
person1.sayName() //Uncaught TypeError: person1.sayName is not a function
Person.sayName() //sayPerson
Copy the code
A static method cannot be called using an instance, whereas the class itself can, and the returned this is the class itself and name is Person.
Class inheritance
Inheritance is very common, so we can avoid a lot of repeated definitions. For example, we defined a person above, so we can define a man on the basis of a person, so that a man can inherit the attributes and methods. Look directly at the code:
Class Man extends Person{} let man1 = new Man("idea",18," Man "); Man1. work // man instanceof Person //trueCopy the code
As you can see, the instance created by Man inherits properties from Person, and Person exists on the stereotype chain of MAN1.
Classes, of course, have other details that won’t be covered here, but they are now the most common way to create objects and instances, and are widely used in the popular React framework.
conclusion
Before the title of this article is “object”, because object is a very big topic, the space is limited, one or two articles may be too much to say, so say separately, in order to reduce everyone’s reading burden.
This article only covers the transition from non-existence of objects/classes to existence, what methods were in existence, what methods are mainly used now, and their features, with some details and object apis that we’ll leave to “later”.
I wish you progress in study and come on together.
Blog link:
【 Talk about the front end 】 Before there is “object”
Series of articles:
【 Talk about the front end 】 good basic skills, easy to learn with me
Small role, big use — variable
Why is everything an object?
[Talking about the front end] Those “unreasonable value” operations
Digital games in JavaScript
【 Talk about front-end 】 “string” jianghu