JSDoc:如何记录 `var self = this;`?

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

只要您使用 this 关键字,JSDoc 就非常擅长获取 class 上定义的方法和属性,例如:

/** @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));
  };
}

这将生成带有 "Class Person" 页面的文档,其中 name 作为成员,greet()logGreeting() 作为方法。

但是当我遇到需要使用 self = this 模式的复杂情况时,事情就开始崩溃了:

/** @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);
  };
}

此示例生成一个 Class Person 页面,但它没有 name 成员或任何方法。

我看到你可以到处使用 @memberof 手动将每个成员和方法附加到 class,但这真的很冗长,我想知道是否有告诉 JSDoc self 指的是 class.

的方法

(顺便说一句,我正在使用 JSDoc 3.4,以防万一。)

@alias 注释可用于告诉 JSDoc 将 self 视为对 class 的引用。实际上,从技术上讲,您需要为 class 的 原型 添加别名 self,而不仅仅是 class 本身的名称(这会破坏内容以我不完全理解的方式)。为此,您需要将 @alias 设置为 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: 从技术上讲,Person# 等同于 Person.prototype,因为尾随 # 指的是对象的原型(据我了解它;开放纠正)。话虽这么说,this实际上指的是一个实例,与原型相同,所以我建议不要使用这种表示法,因为它会使注释更加混乱。幸运的是,原型方法和真正的实例方法之间的 JSDoc 输出没有区别,所以不用太担心 Person# 表示法。

工作相同的备选方案,为了完整性而包括在内,但可能应该避免:

// ...

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

// ...

你让这个(请原谅双关语)变得比它需要的更复杂。你的设计有很多问题。

  1. 您不需要使用this来设置原型。我非常喜欢像这样将对象 InstantiationInitialization 范式分开:

    /** @class */
    function Person() {}
    
    Person.prototype =
    {
        /** This person's name
         * @return {Person}
         */
        init: function(name)
        {
            this.name = name;
    
            return this;
        },
    };
    
  2. 问候已经在一个对象上工作,你不需要传入第二个对象:

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

    这也简化了 logGreeting:

    /** Log a greeting to the browser console */
    logGreeting: function() {
        console.log(this.greet());
    },
    
  3. 但是,假设您确实想要将第二个对象(人)传递给您的logGreeting 它错误地传递了 Class 而不是 Object。应该是这样的:

    /** 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. 您不应该使用 self 来设置原型——您首先误用了使用 self 的原因:它们用于需要引用对象的回调

比如我们添加一个人改名时的回调:

        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 + "'" );
        },

在示例中将所有内容放在一起:

/** @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' );

生成这个 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>

为什么使用变量接收 this 而不是使用带箭头函数的 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) {
    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);
  };
}

如果您想记录 this(例如使用私有方法),您可以使用 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));
}