如何在JavaScript中创建CoffeeScript风格的存在运算符

How to create a CoffeeScript style existential operator in JavaScript?

本文关键字:风格 存在 运算符 CoffeeScript 创建 JavaScript      更新时间:2023-09-26

CoffeeScript将user?.id转换为

if (typeof user !== "undefined" && user !== null) {
   user.id;
}

有可能创建一个JavaScript函数exists来做类似的事情吗?即

exists(user).id

将导致user.idnull

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

不,您不能生成这样的函数。问题是:

any_function(undeclared_variable)

如果未在任何位置声明undeclared_variable,则将生成ReferenceError。例如,如果您运行以下独立代码:

function f() { }
f(pancakes);

您将得到ReferenceError,因为pancakes没有在任何地方声明。演示:http://jsfiddle.net/ambiguous/wSZaL/

然而,typeof运算符可以用于尚未声明的内容,因此:

console.log(typeof pancakes);

将简单地在控制台中记录一个CCD_ 9。演示:http://jsfiddle.net/ambiguous/et2Nv/

如果你不介意可能的ReferenceErrors,那么你的问题中已经有了必要的功能:

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不是。

这个问题很老,但让我思考这个解决方案。

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

我认为在JavaScript中包含可选链接之前,这种功能性方法可能会很有趣(TC39的状态1):

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

wrap()函数用于包装要应用可选链接的对象。在内部,wrap围绕对象创建一个Proxy,并使用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