JavaScript: Introduction to Object-Oriented Programming

Source: Internet
Author: User
Tags prototype definition traits

JavaScript: Introduction to Object-Oriented Programming
Introduction

In this article, we will consider various aspects of object-oriented programming in ECMAScript (although this topic has been discussed in many previous articles ). We will look at these problems more theoretically. In particular, we will consider the object creation algorithm and the relationship between objects (including basic relationships-inheritance, it can also be used in the discussion (I want to eliminate some previous conceptual ambiguity about OOP in JavaScript ).

Overview, paradigm and thoughts 

Before analyzing OOP Technology in ECMAScript, it is necessary to master some basic features of OOP and clarify the main concepts in the overview.

ECMAScript supports structured, object-oriented, functional, and imperative programming methods. In some cases, it also supports Aspect-Oriented Programming, therefore, the definition of object-oriented programming in ECMAScript is given:

ECMAScript is a prototype-based object-oriented programming language.

There are many differences between prototype-based OOP and static classes. Let's take a look at their detailed differences.

Class-based features and prototype-based

Note: The important point in the previous sentence is that it is completely based on static classes. With the word "static", we understand static objects and static classes, which are strongly typed (although not required ).

Many documents on the Forum have stressed that this is the main reason for their opposition to comparing classes and prototypes in JavaScript, although their implementations are different (for example, Python and Ruby Based on Dynamic classes), they are not too opposed to the focus (some conditions are written, although their thoughts are somewhat different, but JavaScript has not become so alternative), but they are opposed to static classes and dynamic prototypes (statics + classes. dynamics + prototypes), specifically, a static class (for example, C ++, JAVA) the mechanism of its subordinates and method definition allows us to see the exact difference between it and prototype-based implementation.

However, let's list them one by one. Let us consider the general principles and the main concepts of these paradigms.

Based on static classes

In the class-based model, there is a concept about classes and instances. Class instances are often named as objects or examples.

Class and Object

Class represents the abstraction of an instance (that is, an object. This is a bit like mathematics, but we call it type or classification ).

For example (here and below are both pseudo code ):

C = Class {a, B, c} // Class C, including features a, B, c

The instance features attributes (Object Description) and methods (Object activity ). The feature itself can also be regarded as an object: whether the attribute is writable, configurable, and configurable (getter/setter. Therefore, the object stores the state (that is, the specific values of all attributes described in a class), and the class defines a strictly unchanged structure (attribute) for their instances) and Strictly unchanged behavior (method ).

C = Class {a, B, c, method1, method2} c1 = {a: 10, B: 20, c: 30} // Class C is an instance: object lifecycle 1c2 = {a: 50, B: 60, c: 70} // Class C is an instance: Object lifecycle 2, with its own status (that is, the attribute value)
Hierarchy inheritance

To improve code reuse, classes can be extended from one to another with additional information added. This mechanism is called hierarchy inheritance.

D = Class extends C = {d, e} // {a, b, c, d, e}d1 = {a: 10, b: 20, c: 30, d: 40, e: 50}

When the caller is on the instance of the class, the native class book usually finds this method. If the method is not found, the parent class is directly searched. If the method is not found, find the parent class of the parent class (such as the strict inheritance chain). If the top of the inheritance class is not found, the result is: the object has no similar behavior, you cannot obtain the results.

d1.method1() // D.method1 (no) -> C.method1 (yes)d1.method5() // D.method5 (no) -> C.method5 (no) -> no result

Compared to copying methods in inheritance to a subclass, attributes are always complicated in the subclass. We can see that subclass D inherits from the parent class C: Attributes a, B, and c are copied, and the structure of D is {a, B, c, d, e }}. However, the method {method1, method2} does not copy the past, but inherits the past. Therefore, if a very deep-level class has attributes that are not required by objects at all, the subclass also has these attributes.

Key concepts of class-based

Therefore, we have the following key concepts:

  1. Before creating an object, you must declare a class. First, you must define its class.
  2. Therefore, this object will be created from the class abstracted into its own "pictogram and similarity" (structure and behavior ).
  3. The method is handled through a strict, direct, and unchanging inheritance chain.
  4. Subclass contains all attributes of the inheritance chain (even if some attributes are not required by the subclass );
  5. Create a class instance. The class cannot (because of the static model) change the features (attributes or methods) of its instance );
  6. An instance (because of its strict static model) does not have any additional behaviors or attributes except the behaviors and attributes declared in the corresponding class of the instance.

    Let's take a look at how to replace the OOP model in JavaScript, that is, the prototype-based OOP we suggest.

    Prototype-based

    The basic concept here is dynamic variable objects. Conversion (complete conversion includes not only values, but also features) is directly related to dynamic languages. The following objects can independently store all their features (attributes, methods) without the need for classes.

    object = {a: 10, b: 20, c: 30, method: fn};object.a; // 10object.c; // 30object.method();

    In addition, due to the dynamic nature, they can easily change (add, delete, and modify) their own features:

    Object. method5 = function (){...}; // Add a new method object. d = 40; // Add the new property ddelete object. c; // Delete the jsonobject property. a = 100; // modify the property attributes. // The result is: object: {a: 100, B: 20, d: 40, method: fn, method5: fn };

    That is to say, when assigning values, if some features do not exist, create them and initialize them. If they exist, they are only updated.

    In this case, code reuse is not implemented through the extension class. (Note that the class cannot be changed because there is no class concept here ), it is implemented through prototype.

    A prototype is an object that is used as the original copy of other objects. If some objects do not have their own necessary features, the prototype can be used as a delegate of these objects as a secondary object.
    Based on Delegation

    Any object can be used as the prototype object of another object, because the object can easily change its prototype dynamics at runtime.

    Note that we are currently considering the introduction rather than the specific implementation. when we discuss the specific implementation in ECMAScript, we will see some of their own characteristics.

    Example (pseudo code ):

    X = {a: 10, B: 20}; y = {a: 40, c: 50}; y. [[Prototype] = x; // x is y's Prototype. a; // 40, features y. c; // 50, which is also a feature of y. b; // 20-Get from prototype: y. B (no)-> y. [[Prototype]. B (yes): 20 delete y. a; // delete your own policy. a; // 10-get z = {a: 100, e: 50} y from the prototype. [[Prototype] = z; // modify the Prototype of y to zy. a; // 100-obtain y from prototype z. e // 50, which is also obtained from the prototype z. q = 200 // Add the new property to the prototype y. q // The modification also applies to y

    This example shows the prototype as an important feature and mechanism of the Secondary object attributes. It is like you need your own attributes. Compared with your own attributes, these attributes are delegated attributes. This mechanism is called delegation and its prototype model is a prototype of delegation (or a prototype based on delegation ). The reference mechanism is called sending information to an object here. If this object does not receive a response, it will be delegated to the prototype for search (requiring it to try to respond to the message ).

    In this case, code reuse is called delegate-based inheritance or prototype-based inheritance. Because any object can be used as a prototype, the prototype can also have its own prototype. These prototypes are connected together to form a so-called prototype chain. A link is also hierarchical in a static class, but it can be easily rearranged to change the hierarchy and structure.

    X = {a: 10} y = {B: 20} y. [[Prototype] = x z = {c: 30} z. [[Prototype] = y z. a // 10 // z. a found in the prototype chain: // z. a (no)-> // z. [[Prototype]. a (no)-> // z. [[Prototype]. [[Prototype]. a (yes): 10

    If an object and its prototype chain cannot respond to message sending, the object can activate the corresponding system signal, which may be processed by other delegation on the prototype chain.

    This system signal is available in many implementations, including dynamic systems: # doesNotUnderstand in Smalltalk and? In Ruby ?? Method_missing; _ getattr __in Python, _ call in PHP, and _ noSuchMethod _ in ECMAScript.

    Example (implementation of ECMAScript of SpiderMonkey ):

    Var object = {// catch the system signal that cannot respond to the Message _ noSuchMethod __: function (name, args) {alert ([name, args]); if (name = 'test') {return '. test () method is handled ';} return delegate [name]. apply (this, args) ;}}; var delegate = {square: function (a) {return a * a ;}}; alert (object. square (10); // 100 alert (object. test ());//. test () method is handled

    That is to say, based on the implementation of static classes, when the message cannot be responded, the conclusion is: the current object does not have certain features, but if you try to get from the prototype chain, it is still possible to get results, or the object has this feature after a series of changes.

    The specific implementation of ECMAScript is to use a delegation-based prototype. However, as we will see from the specifications and implementations, they also have their own characteristics.

    Concatenative Model

    To be honest, it is necessary to say something about another situation (not used in ECMASCript as soon as possible): When the prototype is complicated from other objects, the original replaces the native object. In this case, code reuse refers to the real copying (cloning) of an object during the object creation stage, rather than the delegation. This prototype is called concatenative prototype. Copying all prototype features of an object can further completely change its attributes and methods, and can also change itself as a prototype (in a delegate-based model, this change does not change the existing object behavior, but changes its prototype features ). The advantage of this method is that it can reduce the scheduling and delegation time, but the disadvantage is the memory usage.

    Duck type

    Returning an object with a dynamic weak type change is not related to the type (class) of the object, compared with a static class-based model, it is necessary to check whether the message is related ).

    For example:

    // In a static-based model, if (object instanceof SomeClass) {// some actions are running} // In the dynamic implementation // The type of the object at this time is not important // The mutations, types, and features can be freely changed repeatedly. // Can an important object respond to the test message if (isFunction (object. test) // ECMAScript if object. respond_to? (: Test) // Ruby if hasattr (object, 'test'): // Python

    This is the so-called Dock type. In other words, objects can be identified by their own characteristics during check, rather than the position of objects in the hierarchy or they belong to any specific type.

    Prototype-based Key Concepts

    Let's take a look at the main features of this method:

    1. The basic concept is object.
    2. Objects are completely dynamic (theoretically, they can be converted from one type to another)
    3. Objects do not have strict classes that describe their own structures and behaviors, and objects do not need classes.
    4. Objects do not have class classes, but they can have the original type. If they cannot respond to messages, they can delegate them to the prototype.
    5. The prototype of an object can be changed at any time during running;
    6. In the delegated model, changing the characteristics of the prototype will affect all objects related to the prototype;
    7. In the concatenative prototype model, the prototype is the original copy cloned from other objects and further becomes the original copy completely independent. The transformation of the prototype features does not affect the cloned objects.
    8. If the message cannot be responded, its caller can take additional measures (for example, change scheduling)
    9. The failure of objects can be determined by their layers and classes, but by the current features.

      However, we should also consider another model.

      Based on Dynamic class

      We believe that the difference "class VS prototype" shown in the above example is not so important in this dynamic class-based model (especially if the prototype chain is unchanged, for more accurate differentiation, it is still necessary to consider a static class ). For example, it can also use Python or Ruby (or other similar languages ). These languages use the dynamic-class paradigm. However, in some aspects, we can see some features implemented based on the prototype.

      In the following example, we can see that only the prototype is based on the delegate. We can enlarge a class (prototype) to influence all objects related to this class, we can also dynamically change the class of this object at runtime (provide a new object for the delegate) and so on.

      # Python class A (object): def _ init _ (self, a): self. a = a def square (self): return self. a * self. a = A (10) # create an instance print (. a) #10. B = 20 # provide a new attribute print (. b) #20-access. B = 30 # create a's own attribute print (. b) #30 del. B # Delete attributes print (. b) #20-obtain (prototype) from the class again # It is like a prototype-based model # The prototype class B (object) of the object can be changed at runtime ): # null Class B pass B = B () # B's instance B. _ class _ = A # dynamically changing A class (prototype) B. a = 10 # create a new property print (B. square () #100-A class method available at this time # It can display the reference del Adel B on the delete class # But the object still has an implicit reference, and these methods are still available print (B. square () #100 # But the class cannot be changed at this time # This is the implementation Feature B. _ class _ = dict # error

      The implementation in Ruby is similar: Completely dynamic classes are also used (by the way, in the current version of Python, the comparison with Ruby and ECMAScript is to enlarge the class (prototype) (NO), we can completely change the features of objects (or classes) (adding methods/attributes to classes, and these changes will affect existing objects). However, it cannot dynamically change the class of an object.

      However, this article is not specific to Python and Ruby, so we will continue to discuss ECMAScript itself.

      But before that, let's take a look at some "syntactic sugar" in OOP, because many previous articles on JavaScript often address these issues.

      The only wrong sentence to be noted in this section is: "JavaScript is not a class. It has an original type and can replace the class ". It is very important to know that not all class-based implementations are completely different, even if we may say "JavaScript is different", it is also necessary to consider (except the concept of "class) there are other related features.

      Other features of various OOP implementations

      This section briefly introduces other features and code reuse methods in various OOP implementations, including OOP implementation in ECMAScript. The reason is that the previously mentioned implementation of OOP in JavaScript has some habitual thinking limitations. The only major requirement is that it should be proved technically and ideologically. It cannot be said that JavaScript is not a pure OOP language without discovering the syntax sugar function in other OOP implementations.

      Polymorphism

      Objects in ECMAScript have several meanings of polymorphism.

      For example, a function can be applied to different objects, just like the features of the native object (because the value is determined when the execution context is entered ):

      function test() {  alert([this.a, this.b]);} test.call({a: 10, b: 20}); // 10, 20test.call({a: 100, b: 200}); // 100, 200 var a = 1;var b = 2; test(); // 1, 2

      However, there are also exceptions: the Date. prototype. getTime () method always has a Date object according to the standard value. Otherwise, an exception is thrown.

      alert(Date.prototype.getTime.call(new Date())); // timealert(Date.prototype.getTime.call(new String(''))); // TypeError

      The so-called parameter polymorphism in function definition is equivalent to all data types, but only accepts the polymorphism parameter (such as the. sort sorting method of the array and Its Parameter-the multi-state sorting function ). By the way, the above example can also be considered as a parameter polymorphism.

      The method in the prototype can be defined as null. All created objects should be redefined (implemented) This method (I .e., "one interface (signature), multiple implementations ").

      Polymorphism is related to the Duck type we mentioned above: that is, the object type and position in the hierarchy are not so important, but if it has the necessary features, it is easy to accept (that is, the general interface is very important, and the Implementation can be varied ).

      Encapsulation

      There are often incorrect ideas about encapsulation. In this section, we will discuss some syntactic sugar in OOP implementation, which is also a well-known modifier: in this case, we will discuss some convenient "Sugar" implemented by OOP-well-known modifiers: private, protected, and public (or called object access level or access modifier ).

      Here, I would like to remind you of the main purpose of encapsulation: encapsulation is an increase in abstraction, rather than selecting a hidden "malicious hacker" that writes something directly to your class ".

      This is a big mistake: To hide and hide.

      Access Level (private, protected, and public). In order to facilitate programming, it has been implemented in many object-oriented systems (which is really a very convenient syntax sugar) to describe and build systems in a more abstract way.

      These can be seen in some implementations (such as Python and Ruby ). On the one hand (in Python), these _ private _ protected attributes (by the naming conventions of underlines) are not accessible from outside. On the other hand, Python can access (_ ClassName _ field_name) from outside through special rules ).

      Class A (object): def _ init _ (self): self. public = 10 self. _ private = 20 def get_private (self): return self. _ private # outside: a = A () # print (. public) # OK, 30 print (. get_private () # OK, 20 print (. _ private) # failure, because it can only be used in A # But in Python, you can use special rules to access print (. _ A _ private) # OK, 20

      IN Ruby: on the one hand, it has the ability to define private and protected features. On the other hand, it also has special methods (such as instance_variable_get, instance_variable_set, and send) to obtain encapsulated data.

      Class A def initialize @ a = 10 end def public_method private_method (20) end private def private_method (B) return @ a + B end a =. new # new instance. public_method # OK, 30. a # failed, @ a-is A private instance variable # private_method is private and can only be accessed in Class. private_method # Error # There is a special metadata method name. You can obtain data. send (: private_method, 20) # OK, 30a. instance_variable_get (: @ a) # OK, 10

      The main reason is that the programmer wants to obtain the encapsulated data (note that I do not use "hidden" data in particular. If the data is incorrectly changed or has any errors in some way, it is all the responsibility of the programmer, but it is not a simple "spelling mistake" or "casually changing certain fields ". However, if this happens frequently, it is a bad programming habit and style, because the common value is to use public APIs to "talk" with objects ".

      Repeat, the basic purpose of encapsulation is to abstract the data from the user of the auxiliary data, rather than to prevent hackers from hiding the data. More seriously, encapsulation does not require private modification of data to achieve software security.

      Encapsulate auxiliary objects (local). We use the minimum cost, localization, and predictive changes to ask for the feasibility of public interface behavior changes. This is also the purpose of encapsulation.

      In addition, the setter method ?? The important purpose is to abstract complex computing. For example, the setter element. innerHTML -- abstract statement -- "the HTML in this element is the following content", and the setter function in the innerHTML attribute is difficult to calculate and check. In this case, most of the problems involve abstraction, but encapsulation also occurs.

      The concept of encapsulation is not only related to OOP. For example, it can be a simple function that only encapsulates various computations to make them abstract (there is no need to let users know, such as the Math function. round (......) is implemented, the user just calls it ). It is an encapsulation. Note that it is "private, protected, and public ".

      In the current version of ECMAScript specification, private, protected, and public modifiers are not defined.

      However, in practice, it is possible to see something named "imitating JS encapsulation ". Generally, this context is used (as a rule, constructor itself. Unfortunately, this kind of "Imitation" is often implemented, and programmers can generate pseudo-absolute non-abstract entities to set the "getter/setter method" (I will say it again, it is wrong ):

      function A() {   var _a; // private a   this.getA = function _getA() {    return _a;  };   this.setA = function _setA(a) {    _a = a;  }; } var a = new A(); a.setA(10);alert(a._a); // undefined, privatealert(a.getA()); // 10

      Therefore, everyone understands that for each created object, the getA/setA method is also created, which is also the cause of memory increase (compared with the prototype definition ). Although, in theory, objects can be optimized in the first case.

      In addition, some JavaScript articles often mention the concept of "Private method". Note: The ECMA-262-3 standard does not define any concept about "Private method.

      However, in some cases it can be created in the constructor, because JS is an ideological language-the object is completely mutable and has unique features (under certain conditions in the constructor, some objects can obtain additional methods, but others cannot ).

      In addition, in JavaScript, if the encapsulation is misinterpreted as an understanding that prevents malicious hackers from automatically writing certain values in place of the setter method, the so-called "hidden) "and" private "are not quite" hidden ". Some implementations can call the context to the eval function (which can be tested on SpiderMonkey1.7) obtain the value on the relevant scope chain (and all corresponding variable objects ).

      Eval ('_ a = 100',. getA); // or. setA, because the [[Scope] of the _ a method is. getA (); // 100.

      Alternatively, you can directly access the activity object (such as Rhino) in the Implementation. You can change the value of the internal variable by accessing the corresponding properties of the object:

      // Rhinovar foo = (function () {  var x = 10; // private  return function () {    print(x);  };})();foo(); // 10foo.__parent__.x = 20;foo(); // 20

      Sometimes, in JavaScript, the data "private" and "protected" is achieved by prefixing the variable with an underscore (but this is only a naming convention compared with Python ):

      var _myPrivateData = 'testString';

      It is often used to enclose the execution context in parentheses, but for real auxiliary data, it is not directly associated with the object, but it is convenient to abstract it from external APIs:

      (Function () {// initialize context })();
      Multi-Inheritance

      Multi-inheritance is a convenient syntactic sugar for code reuse and improvement (if we can inherit a class at a time, why cannot we inherit 10 classes at a time ?). However, due to the shortcomings of Multi-inheritance, implementation is not popular.

      ECMAScript does not support multi-inheritance (that is, there is only one object that can be used as a direct prototype), although its ancestor's self-programming language has such capabilities. However, in some implementations (such as SpiderMonkey), _ noSuchMethod _ can be used to manage scheduling and delegation to replace the prototype chain.

      Mixins

      Mixins is a convenient way to reuse code. Mixins has been recommended as a substitute for multi-inheritance. These independent elements can be mixed with any object to expand their functions (So objects can also be mixed with multiple Mixins ). The ECMA-262-3 specification does not define the concept of "Mixins", but according to the Mixins definition and ECMAScript has a dynamic variable object, there is no obstacle to using Mixins to simply expand features.

      Typical Example:

      // helper for augmentationObject.extend = function (destination, source) {  for (property in source) if (source.hasOwnProperty(property)) {    destination[property] = source[property];  }  return destination;}; var X = {a: 10, b: 20};var Y = {c: 30, d: 40}; Object.extend(X, Y); // mix Y into Xalert([X.a, X.b, X.c, X.d]); 10, 20, 30, 40

      Please note that I take these definitions ("mixin", "mix") mentioned in the quotes in the ECMA-262-3, and there is no such concept in the Specification, in addition, it is not a mix but a common method to expand objects through new features. (IN Ruby, the concept of mixins is officially defined. mixin creates a reference containing a module to replace all attributes of this module with simple copying to another module. In fact, the following is true: create an additional object (prototype) for the delegate )).

      Traits

      Traits and mixins have similar concepts, but they have many functions (according to the definition, because mixins can be applied, they cannot contain States because they may cause naming conflicts ). According to ECMAScript, Traits and mixins follow the same principles. Therefore, this specification does not define the concept of "Traits.

      Interface

      The interfaces implemented in some OOP are similar to mixins and traits. However, compared with mixins and traits, the interface must implement the method signature of the class.

      An interface can be considered as an abstract class. However, compared with the abstract class (the methods in the abstract class can only implement one part, and the other part is still defined as a signature), inheritance can only be a single inheritance of the base class, but can inherit multiple interfaces, for this reason, you can use interfaces (multiple mixing) as an alternative to multi-inheritance.

      ECMA-262-3 standards neither define the concept of "interface" nor define the concept of "abstract class. However, as an imitation, it can be implemented by an "empty" method (or an exception thrown in an empty method, telling developers that this method needs to be implemented.

      Object combination

      Object combination is also one of dynamic code reuse technologies. Unlike the inheritance with high flexibility, object combinations implement a dynamic and variable delegate. This is also the basis of the delegated prototype. In addition to the dynamic mutable prototype, this object can be a delegated aggregation object (create a combination as the result -- aggregation), send messages to the object, and delegate to the delegate. This can be more than two delegates, because its dynamic characteristics determine that it can be changed at runtime.

      The example of _ noSuchMethod _ already mentioned is as follows, but it also shows how to explicitly use the delegate:

      For example:

      var _delegate = {  foo: function () {    alert('_delegate.foo');  }}; var agregate = {   delegate: _delegate,   foo: function () {    return this.delegate.foo.call(this);  } }; agregate.foo(); // delegate.foo agregate.delegate = {  foo: function () {    alert('foo from new delegate');  }}; agregate.foo(); // foo from new delegate

      This object relationship is called "has-a", while integration is the "is-a" relationship.

      Due to the lack of display combinations (flexibility compared with inheritance), it is also possible to add intermediate code.

      AOP features

      Function decorators can be used as an aspect-oriented function. ECMA-262-3 specifications do not clearly define the concept of "function decorators" (relative to Python, which is officially defined in Python ). However, functions with function-based parameters can be decorated and activated in some aspects (by applying the so-called recommendations ):

      Example of the simplest decorator:

      function checkDecorator(originalFunction) {  return function () {    if (fooBar != 'test') {      alert('wrong parameter');      return false;    }    return originalFunction();  };} function test() {  alert('test function');} var testWithCheck = checkDecorator(test);var fooBar = false; test(); // 'test function'testWithCheck(); // 'wrong parameter' fooBar = 'test';test(); // 'test function'testWithCheck(); // 'test function'
      Conclusion

      In this article, we have clarified the introduction to OOP (I hope these materials will be useful to you). In the next chapter, we will continue to implement ECMAScript in object-oriented programming.

       

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.