Skip to main content

JS Learning Notes 2_Object-Oriented

Free2015-04-06#JS#js面向对象

JS's OOP is very different from Java and others, its core is prototype. This article analyzes JS inheritance and other parts of OOP in detail.

##1. Definition of Objects##

In ECMAScript, an object is an unordered collection of properties, where "properties" here can be primitive values, objects, or functions

##2. Data Properties and Accessor Properties##

  • Data properties are properties with values, you can set properties as read-only, non-deletable, non-enumerable, etc.

  • Accessor properties are used to set getters and setters. Adding "_" (underscore) before a property name indicates that the property should only be accessed through accessors (private property), but adding an underscore doesn't actually make the property private, this is just a naming convention by habit. Accessor properties are not very useful, for the following reasons:

     var book={
       _year:2004,
       edition:1
     }
     Object.defineProperty(book,"year",{
            get:function(){
               return this._year;
            },
            set:function(newValue){
               if(newValue>2004){
                 this._year=newValue;
                 this.edition+=newValue-2004;
               }
            }
     });
     book.year=2005;
     alert(book.edition);
     /*
     for(var attr in book){
       showLine(attr + ' = ' + book[attr]);
     }
     */
     
    

The example code above is used in "High-Level Programming", the principle is that the _year property of the book object is a data property, while year is an accessor property, using getter and setter can insert read/write control, which sounds good.

But the problem is that _year and edition are both enumerable, meaning they can be seen with a for...in loop, while the accessor property year is non-enumerable. The accessor that should be exposed is not exposed, but the private properties that should be hidden are exposed.

Besides, this way of defining accessors is not fully browser compatible, [IE9+] is the first to fully support it. Of course, there are also ways applicable to old browsers (defineGetter() and defineSetter()), which are quite troublesome.

In short, accessor properties are not very useful.

##3. Constructor Functions##

function Fun(){} var fun = new Fun(); where Fun is a constructor function, there's no difference in declaration from ordinary functions, only the calling method is different (called with the new operator)

Constructor functions can be used to define custom types, for example:

function MyClass(){
  this.attr = value;//member variable
  this.fun = function(){...}//member function
}

It's somewhat similar to Java class declarations, but there are also some differences, for example this.fun is just a function pointer, so it can completely point to other accessible functions (such as global functions), but doing so will break the encapsulation of custom objects

##4. Functions and Prototype##

  1. Declaring a function also creates a prototype object, the function name holds a reference to this prototype object (fun.prototype)

  2. You can add properties to the prototype object, and you can also add properties to instance objects. The difference is that prototype object properties are shared by all instances of that type, while instance object properties are non-shared

  3. When accessing an instance object's property, it first searches in the instance object's scope, and only searches in the prototype object's scope if not found, so instance properties can shadow prototype object properties with the same name

  4. The constructor property of the prototype object is a function pointer, pointing to the function declaration

  5. You can add custom methods to native reference types (Object, Array, String, etc.) through prototypes, for example adding the startsWith method that Chrome doesn't support but FF supports to String:

    var str = 'this is script';
    //alert(str.startsWith('this'));//Error in Chrome
    String.prototype.startsWith = function(strTarget){
      return this.indexOf(strTarget) === 0;
    }
    alert(str.startsWith('this'));//No more error
    

Note: It's not recommended to add prototype properties to native objects, because this might accidentally override native methods, affecting other native code (native code that calls this method)

  1. Inheritance can be achieved through prototypes, the idea is to point the subclass's prototype property to the parent class's instance, to add attributes accessible to the subclass, so after connecting with the prototype chain

    Attributes accessible to subclass
    = subclass instance properties + subclass prototype properties
    = subclass instance properties + parent class instance properties + parent class prototype properties
    = subclass instance properties + parent class instance properties + grandparent class instance properties + grandparent class prototype properties
    = ...
    

Eventually the grandparent...parent class prototype properties are replaced with properties of the prototype object pointed to by Object.prototype

The specific implementation is SubType.prototype = new SuperType(); this can be called simple prototype chain inheritance

##5. Best Way to Create Custom Types (Constructor Pattern + Prototype Pattern)##

Instance properties are declared with constructors, shared properties are declared with prototypes, specific implementation is as follows:

    function MyObject(){
      //instance properties
      this.attr = value;
      this.arr = [value1, value2...];
    }
    MyObject.prototype = {
      constructor: MyObject,//Ensure subclass holds correct constructor reference, otherwise subclass instance's constructor will point to Object's constructor, because we changed the prototype to an anonymous object
      //shared properties
      fun: function(){...}
    }
    

##6. Best Way to Implement Inheritance (Parasitic Combination Inheritance)##

function object(obj){//Returns an object with no instance properties whose prototype is obj
  function Fun(){}
  Fun.prototype = obj;
  return new Fun();
}
function inheritPrototype(subType, superType){
  var prototype = object(superType.prototype);//Establish prototype chain, inherit parent class prototype properties, using custom function object to process is to avoid the parent class instance as subclass prototype having instance properties, simply put, it's to cut off unnecessary instance properties, can compare with combination inheritance to understand
  prototype.constructor = subType;//Ensure constructor is correct, prototype chain will change the constructor reference held by subclass, should change it back after establishing prototype chain
  subType.prototype = prototype;
}

function SubType(arg1, arg2...){
  SuperType.call(this, arg1, arg2...);//Inherit parent class instance properties
  this.attr = value;//subclass instance properties
}
inheritPrototype(SubType, SuperType);

Specific explanations see code comments, this approach avoids the shortcomings of SubType.prototype = new SuperType(); simple prototype chain:

  • The problem of subclass instances sharing parent class instance reference properties (because prototype reference properties are shared by all instances, after establishing the prototype chain, the parent class's instance properties naturally become the subclass's prototype properties).

  • Unable to pass parameters to the parent class constructor when creating subclass instances

##7. Most Common Way to Implement Inheritance (Combination Inheritance)##

Replace the inheritPrototype(SubType, SuperType); statement above with:

SubType.prototype = new SuperType();
SubType.prototype.constructor = SubType;

The disadvantage of this approach is: since the parent class constructor is called twice, there will be a redundant copy of parent class instance properties, specific reasons are as follows:

The first SuperType.call(this); statement copies a copy of parent class instance properties to the subclass as the subclass's instance properties, the second SubType.prototype = new SuperType(); creates a parent class instance as the subclass prototype, at this point this parent class instance has another copy of instance properties, but this copy will be shadowed by the instance properties copied in the first time, so it's redundant.

Comments

No comments yet. Be the first to share your thoughts.

Leave a comment