简体   繁体   中英

JSDoc: How do I document `var self = this;`?

JSDoc is great at picking up on methods and properties defined on a class as long as you use the this keyword, eg:

/** @class */
function Person(name) {
  /** This person's name */
  this.name = name;

  /** Greet someone */
  this.greet = function(person) {
    return 'Hey there, '+person.name;
  };

  /** Log a greeting to the browser console */
  this.logGreeting = function(person) {
    console.log(this.greet(Person));
  };
}

This will generate docs with a "Class Person" page listing name as a Member and greet() and logGreeting() as Methods.

But when I have a complex case where I need to use the self = this pattern, things start to break:

/** @class */
function Person(name) {
  var self = this;

  /** This person's name */
  self.name = name;

  /** Greet someone */
  self.greet = function(person) {
    return 'Hey there, '+person.name;
  };

  /** Log a greeting to the browser console */
  self.logGreeting = function(person) {
    console.log(self.greet(Person));
  };

  /** Log a greeting to the browser console after some delay */
  self.logGreetingDelayed = function(person, delay) {
    setTimeout(function() { self.logGreeting(person); }, delay);
  };
}

This example generates a Class Person page, but it doesn't have the name member or any of the methods.

I've seen that you can use @memberof all over the place to manually attach each member and method to the class, but this is really verbose, and I was wondering whether there's a way to tell JSDoc that self refers to the class.

(Btw, I'm using JSDoc 3.4, in case that's important.)

The @alias annotation can be used to tell JSDoc to treat self as a reference to the class. Actually, technically, you need to alias self to the class's prototype , not just the name of the class itself (that breaks stuff in ways that I don't fully understand). To do this you need to set @alias to Person# 1 :

/** @class */
function Person(name) {
  /** @alias Person# */
  var self = this;

  /** This person's name */
  self.name = name;

  /** Greet someone */
  self.greet = function(person) {
    return 'Hey there, '+person.name;
  };

  /** Log a greeting to the browser console */
  self.logGreeting = function(person) {
    console.log(self.greet(Person));
  };

  /** Log a greeting to the browser console after some delay */
  self.logGreetingDelayed = function(person, delay) {
    setTimeout(function() { self.logGreeting(person); }, delay);
  };
}

1: Technically, Person# is equivalent to Person.prototype , since a trailing # refers to an object's prototype (as I understand it; open to correction). That being said, this actually refers to an instance, which is not the same as the prototype, so I recommend against using this notation, since it makes the comments more confusing. Fortunately, there's no difference in the JSDoc output between a prototype method and a true instance method, so don't worry too much about the Person# notation.

Alternative that works the same, included for completeness, but which should probably be avoided:

// ...

/** @alias Person.prototype */
var self = this;

// ...

You are making this (pardon the pun) more complicated then it needs to be. There are numerous problems with your design.

  1. You don't need to use this to set prototypes. I greatly prefer to separate object Instantiation from Initialization paradigm like this:

     /** @class */ function Person() {} Person.prototype = { /** This person's name * @return {Person} */ init: function(name) { this.name = name; return this; }, };
  2. Greet already works on an object, you doesn't need pass in a 2nd object:

     /** Greet someone */ greet: function() { return 'Hey there, '+this.name; },

    This also simplifies logGreeting :

     /** Log a greeting to the browser console */ logGreeting: function() { console.log(this.greet()); },
  3. However, assuming you do want to pass in a second object (person) to your logGreeting it is incorrectly passing a Class and not an Object . It should be this:

     /** Greet someone */ self.greet = function(person) { return 'Hey there, '+person.name; }; /** Log a greeting to the browser console */ self.logGreeting = function(person) { console.log(self.greet(person)); // BUG: was Person instead of person };
  4. You shouldn't be using self to set prototypes -- you're mis-using the reason for using self in the first place: They are used for callbacks that need an object to refer to.

For example, let's add a callback when a person is renamed:

        rename: function(newName) {
            var self = this;
            var cbRename = function() {
                self.onRename( self.name, newName ); // Why self is needed
                self.name = newName;
            };

            setTimeout( cbRename, 1 );
        },

        /** Callback triggered on rename */
        onRename: function(oldName,newName) {
            console.log( "The person formally known as: '" + oldName + "'" );
            console.log( "Is now known as: '"              + newName + "'" );
        },

Putting it all together in an example:

 /** @class */ function Person() {} Person.prototype = { /** This person's name * @return {Person} */ init: function(name) { this.name = name; return this; }, /** Greet someone */ greet: function() { return 'Hey there, '+this.name; }, /** Log a greeting to the browser console */ logGreeting: function() { console.log(this.greet()); }, rename: function(newName) { var self = this; var cbRename = function() { self.onRename( self.name, newName ); self.name = newName; }; setTimeout( cbRename, 1 ); }, /** Callback triggered on rename */ onRename: function(oldName,newName) { console.log( "The person formally known as: '" + oldName + "'" ); console.log( "Is now known as: '" + newName + "'" ); }, }; var alice = new Person().init( 'Alice' ); console.log( alice ); alice.logGreeting(); alice.rename( 'Bob' );

Which generates this JSdoc3 html:

 <div id="main"> <h1 class="page-title">Class: Person</h1> <section> <header> <h2>Person</h2> </header> <article> <div class="container-overview"> <hr> <h4 class="name" id="Person"><span class="type-signature"></span>new Person<span class="signature">()</span><span class="type-signature"></span></h4> <dl class="details"> <dt class="tag-source">Source:</dt> <dd class="tag-source"><ul class="dummy"><li> <a href="person.js.html">person.js</a>, <a href="person.js.html#line2">line 2</a> </li></ul></dd> </dl> </div> <h3 class="subsection-title">Methods</h3> <hr> <h4 class="name" id="greet"><span class="type-signature"></span>greet<span class="signature">()</span><span class="type-signature"></span></h4> <div class="description"> Greet someone </div> <dl class="details"> <dt class="tag-source">Source:</dt> <dd class="tag-source"><ul class="dummy"><li> <a href="person.js.html">person.js</a>, <a href="person.js.html#line17">line 17</a> </li></ul></dd> </dl> <hr> <h4 class="name" id="init"><span class="type-signature"></span>init<span class="signature">()</span><span class="type-signature"> → {<a href="Person.html">Person</a>}</span></h4> <div class="description"> This person's name return {Person} </div> <dl class="details"> <dt class="tag-source">Source:</dt> <dd class="tag-source"><ul class="dummy"><li> <a href="person.js.html">person.js</a>, <a href="person.js.html#line9">line 9</a> </li></ul></dd> </dl> <hr> <h4 class="name" id="logGreeting"><span class="type-signature"></span>logGreeting<span class="signature">()</span><span class="type-signature"></span></h4> <div class="description"> Log a greeting to the browser console </div> <dl class="details"> <dt class="tag-source">Source:</dt> <dd class="tag-source"><ul class="dummy"><li> <a href="person.js.html">person.js</a>, <a href="person.js.html#line23">line 23</a> </li></ul></dd> </dl> <hr> <h4 class="name" id="onRename"><span class="type-signature"></span>onRename<span class="signature">()</span><span class="type-signature"></span></h4> <div class="description"> Callback triggered on rename </div> <dl class="details"> <dt class="tag-source">Source:</dt> <dd class="tag-source"><ul class="dummy"><li> <a href="person.js.html">person.js</a>, <a href="person.js.html#line38">line 38</a> </li></ul></dd> </dl> </article> </section> </div>

Why use a variable to receive this instead to use this with arrow function?

/** @class */
function Person(name) {

  /** This person's name */
  this.name = name;

  /** Greet someone */
  this.greet = function(person) {
    return 'Hey there, '+person.name;
  };

  /** Log a greeting to the browser console */
  this.logGreeting = function(person) {
    console.log(this.greet(person));
  };

  /** Log a greeting to the browser console after some delay */
  this.logGreetingDelayed = function(person, delay) {
    setTimeout(() => this.logGreeting(person), delay);
  };
}

And if you want to document this (to use private methods for instance) you can use JSDoc @this :

/** @class */
function Person(name) {

  /** This person's name */
  this.name = name;

  /** Greet someone */
  this.greet = function(person) {
    return 'Hey there, '+person.name;
  };

  /** Log a greeting to the browser console */
  this.logGreeting = function(person) {
    privateLogGreeting.call(this, person);
  };

  /** Log a greeting to the browser console after some delay */
  this.logGreetingDelayed = function(person, delay) {
    setTimeout(() => this.logGreeting(person), delay);
  };
}
//Private method
/**
 * @this Person
 * @param {Person} person
 */
function privateLogGreeting(person) {
  console.log(this.greet(person));
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM