Object-oriented
What is object-oriented programming?
Object-oriented is a programming idea that is often compared with process-oriented.
The process-oriented focus is on verbs, which analyze the steps needed to solve a problem, write functions to implement each step, and call functions in turn.
Object-oriented focuses on the subject and predicate, breaking down the things that make up the problem into individual objects, and the purpose of breaking down objects is not to achieve a certain step, but to describe the various behaviors of this thing in the current problem.
What are the characteristics of object-oriented?
Encapsulation: Let the user of the object not consider the internal implementation, only consider the use of functions to protect the internal code, leaving only some api interfaces for the user to use
Inheritance: For code reuse, methods and attributes are inherited from the parent class, and subclasses have their own attributes
Polymorphism: Different objects act on the same operation to produce different effects. Polymorphic thinking actually separates what you want to do from who does it
Examples of the process of chess are as follows:
<Process Oriented>This is the following:Start->White Chess->Board Display->Check Win or Lose->Black Chess->Board Display->Check Win or Lose->Cycle
Code representation of a possible sequence of function calls
init();
whitePlay(); //Inside for a Chess-playing operation
repaint(); //board display
check();
blackPlay(); //Do a single chess play again
repaint(); //board display
check();
<Object Oriented>This is the following:Checkerboard.Opening->Player.Chess->Checkerboard.Re-display->Checkerboard.Check wins->Players.Checkerboard->Checkerboard.Re-display->Checkerboard.Check wins and losses
Code may indicate this
const checkerBoard = new CheckerBoard(); // CheckerBoard class closes the operations of the board internally, such as initializing the board, checking the winning-losing relationship, etc.
Const whitePlayer = new Player ('white'); the // Player class encapsulates the actions of various players, such as waiting, losing, repentance
const blackPlayer = new Player('black');
whitePlayer.start(); // start method end, internally encapsulated or triggered calls to checkerBoard.repaint(), checkerBoard.check() via event Publishing
blackPlayer.start();
You just need to call a new player and then the start method, which means we just need to focus on the behavior and not know what's going on inside.
And if you want to add some new features, such as Regret Chess, such as another player, object-oriented is very good to expand.
In the example above, how does the object-oriented feature work?
Encapsulation: Player, CheckerBoard class, use without knowing what's implemented internally, just consider exposed api usage
Inheritance: Both whitePlayer and blackPlayer inherit from Player and can use Player's methods and properties directly
Polymorphism: The colors of whitePlayer.start() and blackPlayer.start() are white and black, respectively.
When is object-oriented appropriate
It can be seen that in the face of more complex problems, or when there are more participants, object-oriented programming ideas can simplify the problem well, and can be better extended and maintained.
In the face of simpler problems, the difference between object-oriented and process-oriented is not obvious, and can be invoked step by step.
Object-oriented in Js
What does an object contain
Method, Property
Some built-in objects
Object Array Date Function RegExp
create object
1. Common way
Each new object rewrites the assignments of color and start
const Player = new Object(); Player.color = "white"; Player.start = function () { console.log("white Play chess"); };
Or factory mode, neither of which recognizes object types, such as Player's type being Object only
function createObject() { const Player = new Object(); Player.color = "white"; Player.start = function () { console.log("white Play chess"); }; return Player; }
2. Constructor/Instance
Attributes and methods added through this always point to the current object, so when instantiating, the attributes and methods added through this copy in memory, which results in [memory waste].
But the advantage of creating this is that even if you change the properties or methods of one object, it doesn't affect other objects (because each object is a copy)
function Player(color) { this.color = color; this.start = function () { console.log(color + "Play chess"); }; } const whitePlayer = new Player("white"); const blackPlayer = new Player("black");
Tips.How do you see if a function has been created in memory many times?
For example, in the constructor, we can see whitePlayer.start == blackPlayer.start//output false
3. Prototype
The method of inheritance through a prototype is not its own. We look one level at the prototype chain, so the benefit of creating it is that it is only created once in memory, and the instantiated objects point to the prototype object.
function Player(color) { this.color = color; } Player.prototype.start = function () { console.log(color + "Play chess"); }; const whitePlayer = new Player("white"); const blackPlayer = new Player("black");
4. Static Properties
Is an attribute method bound to a constructor and needs to be accessed through the constructor
For example, we want to see an example of how many players were created in total
function Player(color) { this.color = color; if (!Player.total) { Player.total = 0; } Player.total++; } let p1 = new Player("white"); console.log(Player.total); // 1 let p2 = new Player("black"); console.log(Player.total); // 2
Can be cached, can be a public object on the parent
Prototype Prototype Chain
What are the benefits of adding attributes or methods to a prototype?
Without prototyping, every time a new object is generated, a new storage space is opened up in memory, and when there are more objects, performance becomes poor.
But through
Player.prototype.xx = function () {}; Player.prototype.xx = function () {};
Adding attributes or methods to a prototype object in this way can be cumbersome. So we can write like this
Player.prototype = { start: function () { console.log("Play chess"); }, revert: function () { console.log("Regret Chess"); }, };
How do I find Player's prototype object?
function Player(color) { this.color = color; } Player.prototype.start = function () { console.log(color + "Play chess"); }; const whitePlayer = new Player("white"); const blackPlayer = new Player("black"); console.log(blackPlayer.__proto__); // Player {} console.log(Object.getPrototypeOf(blackPlayer)); // Player {}, can get u through Object.getPrototypeOf proto_u console.log(Player.prototype); // Player {} console.log(Player.__proto__); // [Function]
Prototype flowchart
What the new keyword does
1. A new object whitePlayer inherited from Player.prototype was created
2. whitePlayer. u Proto_u Point to Player.prototype, whitePlayer. u Proto_u = Player.prototype
3. Point this to the newly created object whitePlayer
4. Return a new object
4.1 Return this if the constructor does not explicitly return a value
4.2 Return this if the constructor has an explicit return value and is a base type, such as number,string,boolean
4.3 If the constructor has an explicit return value, which is the object type, such as {a:1}, then it returns this object {a:1}
Handwriting a new function
// 1. A new object obj is created as new Object() // 2. Take out the first parameter, which is the constructor we want to pass in. Moreover, because shift modifies the original array, arguments are stripped of the first parameter // 3. Point obj's prototype to the constructor so that OBJ can access the properties in the constructor's prototype // 4. Use apply to change the direction of the constructor this to the newly created object so that obj can access the properties in the constructor // 5. Return to obj function objectFactory() { let obj = new Object(); let Constructor = [].shift.call(arguments); // Pseudo-array These two rows can be generated with Object.create, let Constructor = Object.create (passed in constructor.prototype); obj.__proto__ = Constructor.prototype; let ret = Constructor.apply(obj, arguments); return typeof ret === "object" ? ret : obj; }
Note Object.create(null) is cleanest when creating objects
What is the prototype chain
When reading the properties of an instance, if it cannot be found, it will look for the properties in the prototype associated with the object, and if not, it will look for the prototype of the prototype until it reaches the top level.
For instance
function Player() {} Player.prototype.name = "Kevin"; var p1 = new Player(); p1.name = "Daisy"; // Find the name attribute in the p1 object, because name is added above, so the output "Daisy" console.log(p1.name); // Daisy delete p1.name; // Delete p1.name, then look for P1 and find that there is no name attribute, it will be from p1's prototype p1. u Proto_u Find it, Player.prototype, then find the name and output "Kevin" console.log(p1.name); // Kevin
So if we can't find the name attribute in Player.prototype, we'll go to Player.prototype. u Proto_u To find, that is {}.
Object.prototype.name = "root"; function Player() {} Player.prototype.name = "Kevin"; var p1 = new Player(); p1.name = "Daisy"; // Find the name attribute in the p1 object, because name is added above, so the output "Daisy" console.log(p1.name); // Daisy delete p1.name; // Delete p1.name, then look for P1 and find that there is no name attribute, it will be from p1's prototype p1. u Proto_u Find it, Player.prototype, then find the name and output "Kevin" console.log(p1.name); // Kevin delete Player.prototype.name; console.log(p1.name);
Such a passage through u proto_u The chain of objects to which prototype is connected is the prototype chain
inherit
Prototype Chain Inheritance
Realization
function Parent() { this.name = "parentName"; } Parent.prototype.getName = function () { console.log(this.name); }; function Child() {} // Parent's instance contains both instance and prototype attribute methods, so assign new Parent() to Child.prototype. // If only Child.prototype = Parent.prototype, then Child can only call getName, not.name. // When Child.prototype = new Parent(), if new Child() gets an instance object child, then // child.__proto__ === Child.prototype; // Child.prototype.__proto__ === Parent.prototype // This means that when accessing the properties of the child object, if it cannot be found on the child, it will go to Child.prototype and if it cannot be found, it will go to Parent.prototype to achieve inheritance. Child.prototype = new Parent(); // Because the constructor attribute is included in the prototype and the prototype is reassigned above, it causes the Child constructor to point to [Function: Parent], which sometimes causes problems when using child1.constructor to determine the type // To ensure the correct type, we need to point Child.prototype.constructor at his original constructor, Child Child.prototype.constructor = Child; var child1 = new Child(); child1.getName(); // parentName
Implicit problems
1. If an attribute is of reference type, once an instance modifies the attribute, all instances will be affected
function Parent() { this.actions = ["eat", "run"]; } function Child() {} Child.prototype = new Parent(); Child.prototype.constructor = Child; const child1 = new Child(); const child2 = new Child(); child1.actions.pop(); console.log(child1.actions); // ['eat'] console.log(child2.actions); // ['eat']
2. Child instances cannot be passed along when they are created
Constructor Inheritance
Question 1 above, how to solve it?
Can you find a way to add attribute methods on Parent to Child? Instead of having prototype objects on them, prevent them from being shared by all instances.
###Implementation
Question 1. We can use call to copy the operation on Parent once
function Parent() { this.actions = ["eat", "run"]; this.name = "parentName"; } function Child() { Parent.call(this); } const child1 = new Child(); const child2 = new Child(); child1.actions.pop(); console.log(child1.actions); // ['eat'] console.log(child1.actions); // ['eat', 'run']
Targeting Question 2. How should we pass on the information?
function Parent(name, actions) { this.actions = actions; this.name = name; } function Child(id, name, actions) { Parent.call(this, name, actions); // If you want to pass multiple parameters directly, you can Parent.apply(this, Array.from(arguments).slice(1)); this.id = id; } const child1 = new Child(1, "c1", ["eat"]); const child2 = new Child(2, "c2", ["sing", "jump", "rap"]); console.log(child1.name); // { actions: [ 'eat' ], name: 'c1', id: 1 } console.log(child2.name); // { actions: [ 'sing', 'jump', 'rap' ], name: 'c2', id: 2 }
Implicit problems
Attributes or methods can only be defined in a constructor if they want to be inherited. If the method is defined within the constructor, it will be created once for each instance, taking up an extra block of memory.
function Parent(name, actions) { this.actions = actions; this.name = name; this.eat = function () { console.log(`${name} - eat`); }; } function Child(id) { Parent.apply(this, Array.prototype.slice.call(arguments, 1)); this.id = id; } const child1 = new Child(1, "c1", ["eat"]); const child2 = new Child(2, "c2", ["sing", "jump", "rap"]); console.log(child1.eat === child2.eat); // false
Combinatorial Inheritance
We implement basic inheritance through prototype chain inheritance, where methods exist on the prototype and subclasses can be called directly. However, attributes of reference types are shared by all instances and cannot be passed.
With constructor inheritance, we solved the two problems above: using call to repeat the creation of attributes and methods within a sub-constructor, and it can be passed.
However, a problem with constructors is that the method of repeated creation within the constructor causes an excessive memory footprint.
Prototype chain inheritance solves the problem of duplicate creation, so we combine the two approaches, called combinatorial inheritance
Realization
function Parent(name, actions) { this.name = name; this.actions = actions; } Parent.prototype.eat = function () { console.log(`${this.name} - eat`); }; function Child(id) { // (id, ...args) Parent.apply(this, args); Parent.apply(this, Array.from(arguments).slice(1)); // For the first time this.id = id; } Child.prototype = new Parent(); // The second time Child.prototype.constructor = Child; const child1 = new Child(1, "c1", ["hahahahahhah"]); const child2 = new Child(2, "c2", ["xixixixixixx"]); child1.eat(); // c1 - eat child2.eat(); // c2 - eat console.log(child1.eat === child2.eat); // true
Implicit problems
The constructor was called twice and repeated
1. Parent.apply(this, Array.from(arguments).slice(1));
2. Child.prototype = new Parent();
Parasitic Combinatorial Inheritance
The constructor has been called twice above. Think about which step we can simplify?
We could consider giving Child.prototype indirect access to Parent.prototype
Realization
function Parent(name, actions) { this.name = name; this.actions = actions; } Parent.prototype.eat = function () { console.log(`${this.name} - eat`); }; function Child(id) { Parent.apply(this, Array.from(arguments).slice(1)); this.id = id; } // Simulate the effect of Object.create // If Object.create is used directly, it can be written as Child.prototype = Object.create(Parent.prototype); let TempFunction = function () {}; TempFunction.prototype = Parent.prototype; Child.prototype = new TempFunction(); Child.prototype.constructor = Child; const child1 = new Child(1, "c1", ["hahahahahhah"]); const child2 = new Child(2, "c2", ["xixixixixixx"]);
Perhaps some students will ask why Child.prototype has to be accessed to Parent.prototype through a bridge?
Can't direct Child.prototype = Parent.prototype?
A: No!!
Let's have a look
function Parent(name, actions) { this.name = name; this.actions = actions; } Parent.prototype.eat = function () { console.log(`${this.name} - eat`); }; function Child(id) { Parent.apply(this, Array.from(arguments).slice(1)); this.id = id; } Child.prototype = Parent.prototype; Child.prototype.constructor = Child; console.log(Parent.prototype); // Child { eat: [Function], childEat: [Function] } Child.prototype.childEat = function () { console.log(`childEat - ${this.name}`); }; const child1 = new Child(1, "c1", ["hahahahahhah"]); console.log(Parent.prototype); // Child { eat: [Function], childEat: [Function] }
You can see that when you add a new attribute or method to Child.prototype, the Parent.prototype also changes, which is not what we want to see.
ES6 Inheritance
class Parent { constructor() { this.name = 'aaa'; } getName() { console.log('getname'); } } class Child extends Parent { constructor() { super(); } } const p1 = new Child(); p1.getName();
Learning makes people progress, will repeated learning make them progress?