如何在JavaScript中创建一个类似于CoffeeScript风格的存在运算符?

9
CoffeeScript将user?.id转换为:
if (typeof user !== "undefined" && user !== null) {
   user.id;
}

是否有可能创建一个名为 exists 的 JavaScript 函数,实现类似的功能?即:

exists(user).id

这将导致user.idnull

如果一个函数接受另一个参数,即exists(user, 'id'),那么会更容易,但这看起来不太好。


为了实现这个,我们需要通用的getter函数(definegetter(o, function(property){...})),如果我没记错的话,这并不是JavaScript规范的一部分。 - Zeta
@Zeta 不完全是,因为访问对象的不存在属性会返回 undefined 而不是失败,这应该已经足够了。 - millimoose
3
我能想到的最接近的函数是 function exists(obj) { if (obj) return obj; return {}; },但它不能处理未定义的变量。JavaScript 不支持这种语法扩展。 - millimoose
你可能会遇到一个问题,即user未定义(与值undefined不同),这将导致不同的行为。你可以只是声明类似于var user;这样的东西,然后它就没问题了,但这会创建相当多的混乱。 - Qantas 94 Heavy
1
请查看 lodash 的 get 函数。 - tusharmath
3个回答

5
不,你无法编写这样的函数。问题在于这个代码段:
any_function(undeclared_variable)

如果undeclared_variable未在任何地方声明,则会产生ReferenceError。例如,如果您运行此独立代码:

function f() { }
f(pancakes);

如果没有声明pancakes,您将收到一个ReferenceError错误。示例: http://jsfiddle.net/ambiguous/wSZaL/

然而,typeof运算符可以用于尚未声明的内容,所以可以这样使用:

console.log(typeof pancakes);

如果不介意可能出现的引用错误,那么您已经在问题中拥有必要的函数:

将仅在控制台中记录undefined。演示: http://jsfiddle.net/ambiguous/et2Nv/

function exists(obj, key) {
    if (typeof obj !== "undefined" && obj !== null)
        return obj[key];
    return null; // Maybe you'd want undefined instead
}

或者,既然您在这里不需要能够对未声明的变量使用 typeof,您可以将其简化为:

function exists(obj, key) {
    if(obj != null)
      return obj[key];
    return null;
}

请注意 != 的变化,即使 undefined === null 不成立,undefined == null 仍然为真。

2

这是一个很老的问题,但让我想到了一个解决方案。

exists = (obj) => obj || {}
exists(nullableObject).propName;

不行。引用coffeescript.org的话:“在JavaScript中检查变量是否存在有点困难。if(variable)...接近,但对于零,空字符串和false(仅举几个最常见的情况)失败。” - JasonWoof

0

我认为这种函数式方法可能很有趣,直到可选链在JavaScript中被包含进来(TC39的第一阶段)

使用代理和Maybe单子,您可以实现在失败时返回默认值的可选链。

一个wrap()函数用于包装您想要应用可选链的对象。在内部,wrap创建一个围绕您的对象的代理,并使用Maybe包装器来处理缺失的值。

在链的末尾,通过将getOrElse(default)与默认值链接起来,您可以解开该值,当链无效时返回默认值:

const obj = {
  a: 1,
  b: {
    c: [4, 1, 2]
  },
  c: () => 'yes'
};

console.log(wrap(obj).a.getOrElse(null)) // returns 1
console.log(wrap(obj).a.b.c.d.e.f.getOrElse(null)) // returns null
console.log(wrap(obj).b.c.getOrElse([])) // returns [4, 1, 2]
console.log(wrap(obj).b.c[0].getOrElse(null)) // returns 4
console.log(wrap(obj).b.c[100].getOrElse(-1)) // returns -1
console.log(wrap(obj).c.getOrElse(() => 'no')()) // returns 'yes'
console.log(wrap(obj).d.getOrElse(() => 'no')()) // returns 'no'

wrap(obj).noArray.getOrElse([1]).forEach(v => console.log(v)) // Shows 1
wrap(obj).b.c.getOrElse([]).forEach(v => console.log(v)) // Shows 4, 1, 2

完整的示例:

class Maybe {
  constructor(value) {
    this.__value = value;
  }
  static of(value){
    if (value instanceof Maybe) return value;
    return new Maybe(value);
  }
  getOrElse(elseVal) {
    return this.isNothing() ? elseVal : this.__value;
  }
  isNothing() {
    return this.__value === null || this.__value === undefined;
  }
  map(fn) {  
    return this.isNothing()
      ? Maybe.of(null)
      : Maybe.of(fn(this.__value));
  }
}

function wrap(obj) {
  function fix(object, property) {
    const value = object[property];
    return typeof value === 'function' ? value.bind(object) : value;
  }
  return new Proxy(Maybe.of(obj), {
    get: function(target, property) {
      if (property in target) {
          return fix(target, property);
      } else {
        return wrap(target.map(val => fix(val, property)));
      }
    }
  });
}

const obj = { a: 1, b: { c: [4, 1, 2] }, c: () => 'yes' };

console.log(wrap(obj).a.getOrElse(null))
console.log(wrap(obj).a.b.c.d.e.f.getOrElse(null))
console.log(wrap(obj).b.c.getOrElse([]))
console.log(wrap(obj).b.c[0].getOrElse(null))
console.log(wrap(obj).b.c[100].getOrElse(-1))
console.log(wrap(obj).c.getOrElse(() => 'no')())
console.log(wrap(obj).d.getOrElse(() => 'no')())

wrap(obj).noArray.getOrElse([1]).forEach(v => console.log(v)) // Shows 1
wrap(obj).b.c.getOrElse([]).forEach(v => console.log(v)) // Shows 4, 1, 2


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接