jerytang

2015-12-21 17:13

【译】 javascript 函数参数设计

本文作者:IMWeb jerytang 原文出处:IMWeb社区 未经同意,禁止转载

本文 编译https://gcanti.github.io/2014/09/25/six-reasons-to-define-constructors-with-only-one-argument.html

原文标题为:构造器只使用一个参数的 6 个原因 (Six reasons to define constructors with only one argument)

简介

Reddit 上 有关于本文的评论。

JavaScript中,通常像下面这样来定义一个 "class" :

function VanillaPerson(name, surname) { // multiple arguments
  this.name = name;
  this.surname = surname;
}

var person = new VanillaPerson('Giulio', 'Canti');
person.name; // => 'Giulio'

如果构造器只用一个参数,改写上面的代码如下:

function Person(obj) { // only one argument
  this.name = obj.name;
  this.surname = obj.surname;
}

var person = new Person({name: 'Giulio', surname: 'Canti'});
person.name; // => 'Giulio'

下面,我将列出 6 个方面谈谈,在排除对性能有极端要求的情况下,为什么后者是更好的方案。

1. 维护性

如果 Person 添加了一个参数,先看第一种方案要多少处修改:

/**
 ...
 * @param {String} email // change
 ...
 */
function VanillaPerson(name, surname, email) { // change

  // make `new` optional
  if (!(this instanceof VanillaPerson)) {
    return new VanillaPerson(name, surname, email); // change
  }

  this.name = name;
  this.surname = surname;
  this.email = email; // change
}

再看,使用一个 object 参数,仅需要改写一处:

function Person(obj) {

  if (!(this instanceof Person)) {
    return new Person(obj);
  }

  this.name = obj.name;
  this.surname = obj.surname;
  this.email = obj.email; // change
}

许多人认为,构造器中使用 new 是一种 "反模式",但是我感觉还 OK. 通常用 new 能更明确的表示实例化一个实例,但是我依然会像下面这样写, $('.myclass').show() ,而不是,new $('.myclass').show() ,尽管前者是一种 "反模式"。

2. 命名参数 (Named parameters)

JavaScript是不支持命名参数【1】,

// first argument is name or surname? I don't remember
var person = new VanillaPerson('Canti', 'Giulio'); // wrong!

使用一个 object 参数能很好的模拟命名参数,虽然要多些点代码,但是更加易读

// order doesn't matter and it's more readable
var person = new Person({surname: 'Canti', name: 'Giulio'});

3. 控制可选参数

采用多参数方案,当有可选参数的时候,将会非常丑陋:

function VanillaPerson(name, surname, email, vat, address) {
  this.name = name;
  this.surname = surname;
  this.email = email;
  this.vat = vat;
  this.address = address;
}

// I must count the arguments to know where to put 'myaddress'
var person = new VanillaPerson('Giulio', 'Canti', null, 'myaddress'); // wrong!

而使用单个 object 参数,将非常方便:

var person = new Person({surname: 'Canti', name: 'Giulio', address: 'myaddress'});

4. 使用 json 数据的方便性

假设你从某个 API 通过 ajax 获取的 JSON 数据,

{
  "name": "Giulio",
  "surname": "Canti"
}

使用多参数方案的话,你需要定义一个函数来处理:

function deserialize(x) {
  return new VanillaPerson(x.name, x.surname);
}

var person = deserialize(json);

使用单个 object 参数的方案,直接就可以使用:

var person = new Person(json);

更多关于这个点的讨论,参见另一篇 blog,JSON Deserialization Into An Object Model.

5. 幂等性

当函数 f 满足 f(f(x)) = f(x) ,称 f 具有幂等性。

上面的多参数函数不是幂等的,但是可以很容易的让 object 参数的函数变为幂等的:

function Person(obj) {

  if (obj instanceof Person) {
    return obj;
  }

  ...
}

var person = new Person({name: 'Giulio', surname: 'Canti'});
new Person(person) === person; // => true

6. 避免重复

如果你需要建立各种模型,并且需要对模型的字段进行验证,使用单个 object 参数,实现一个如下的函数,可以节省每次实例化时的验证:

function struct(props) {

  function Struct(obj) {

    // make Struct idempotent
    if (obj instanceof Struct) return obj;

    // make `new` optional, decomment if you agree
    // if (!(this instanceof Struct)) return new Struct(obj);

    // add props
    for (var name in props) {
      if (props.hasOwnProperty(name)) {
        // here you could implement type checking exploiting props[name]
        this[name] = obj[name];
      }
    }

    // make the instance immutable, decomment if you agree
    // Object.freeze(this);

  }

  // keep a reference to meta infos for further processing,
  // documentation tools and IDEs support
  Struct.meta = {
    props: props
  };

  return Struct;

}

// defines a 1-arity Person class
var Person = struct({
  name: String,
  surname: String
});

var person = new Person({surname: 'Canti', name: 'Giulio'});

上面实现了一个 Person 类,要求 surname 和 name 字段都必须为字符串,struct 是一个通用的实现,里面完成了对传入的 object 的所有验证逻辑。

延伸阅读

(译注,第 6 点,作者专门写了一篇 blog ,实现了一个非常有意思的验证库【2】)

JavaScript, Types and Sets - Part I

github

使用单个 object 作为参数的特性实现 tcomb .

tcomb可以用于浏览器和 Node.js ,用于 javascript的类型检查,适合 Domain Driven Design ,增加代码内部安全性。

(译注,但是封装是有代价的【3】)

备注

【1】译注:对于支持 Named parameters 的语言,你可以写成下面这样,函数内部是根据名字而不是位置来引用参数

var person = new VanillaPerson(surname='Canti', name='Giulio');

【2】译注:https://github.com/gcanti

【3】译注:参见 https://gcanti.github.io/2014/09/25/six-reasons-to-define-constructors-with-only-one-argument.html 的评论

I don't like this for a performance reasons. You are creating extra object just to pass values and it is garbage collected later. It doesn't matter for a few instances, but doing hundreds or thousands instances this way and you would feel small hiccups, especially in performance sensitive application like games.

每次函数调用都传入了一个额外的 object ,增加了垃圾回收的负担,实例少的时候还可以接受,但是,实例数量一多,必然会带来无问题。

【4】原文下评论中也指出 ExtJs 早就这样干了,ExtJS 的参数不就是一个大大的 json config.

0条评论

    您需要 注册 一个IMWeb账号或者 才能进行评论。