Javascript: 在对象闭包中使用访问器属性

Javascript: Using accessor properties in object closures

我最近通过阅读 this article 了解了如何使用函数将对象组合在一起。接下来,我得到了这段代码:

function withFlying(o) {
  let _isFlying = false;
  return {
    ...o,
    fly () {
      _isFlying = true;
    },
    land () {
      _isFlying = false;
    },
    isFlying () {
        return _isFlying
    }
  }
};

function withWalking(o) {
    let isWalking = false;
    return {
        ...o,
        startWalking() {
            isWalking = true;
            return this
        },
        stopWalking() {
            isWalking = false;
            return this
        },
        isWalking: () => isWalking
    }
}

const bird = withWalking(withFlying({}))

这里一切正常。但是,我希望能够将 isFlying 作为 属性 而不是函数来调用:

// current (working)
bird.isFlying() // return value of `_isFlying`

// desired
bird.isFlying // return value of `_isFlying`

我知道 getset 是可以在对象字面量中使用的关键字,所以我尝试了这个:

function withFlying(o) {
  let _isFlying = false
  return {
    ...
    get isFlying () {
      return _isFlying
    }
  }
}

但是在使用其他函数更新后它没有显示正确的值。我认为 get 属性 是一个函数,闭包的应用类似于其他函数。我这个假设错了吗? get 是否存在我不理解的潜在行为,我正在尝试以我现在的方式实现可能的目标吗?

这是我尝试使用的代码片段:

function withFlying(o) {
  let _isFlying = false;
  return {
    ...o,
    fly () {
      _isFlying = true;
    },
    land () {
      _isFlying = false;
    },
    valueOf_isFlying() {
      return _isFlying;
    },
    get isFlying () {
      return _isFlying
    }
  }
};

function withWalking(o) {
  let isWalking = false;
  return {
    ...o,
    startWalking() {
        isWalking = true;
        return this
    },
    stopWalking() {
        isWalking = false;
        return this
    },
    isWalking: () => isWalking
  }
}


const bird = withWalking(withFlying({}))

// desired
console.log(bird.isFlying) // _isFlying starts false
bird.fly() // should set _isFlying to true
console.log(bird.isFlying) // still returns false
console.log(bird.valueOf_isFlying()) // shows _isFlying is true

问题是当您创建新对象时,您正在使用展开符号从原始对象复制属性:

return {
    ...o,
    // ...
};

问题在于它复制了访问器属性的 当时-当前值,而不是访问器 属性 的定义。你可以在这里看到:

const obj1 = {
    get example() {
        return 42;
    }
};
console.log("Notice that the property descriptor is for an accessor property:");
console.log(Object.getOwnPropertyDescriptor(obj1, "example"));
const obj2 = {...obj1};
console.log("Notice that the property descriptor is for a simple data property:");
console.log(Object.getOwnPropertyDescriptor(obj2, "example"));
.as-console-wrapper {
    max-height: 100% !important;
}

这很像你所做的:

for (const key of Object.keys(o) {
    newObject[key] = e[key];
}

e[key] 获取 属性 的当前值,而不是 属性.

的定义

要修复它,请使用 Object.getOwnPropertyDesciptors 获取属性的描述符,并使用 Object.defineProperties 在新对象上定义相同的属性。由于您至少在两个地方这样做(并添加了更多属性),您可能需要一个效用函数:

function assignPropertyDescriptors(target, obj, updates) {
    // B
    return Object.defineProperties(
        // A
        Object.defineProperties(
            target,
            Object.getOwnPropertyDescriptors(obj)
        ),
        Object.getOwnPropertyDescriptors(updates)
    );
}

"A" Object.defineProperties 调用复制原始对象的 属性 描述符并将它们应用于新对象。 "B" Object.defineProperties 调用也将您添加到该新对象的对象应用。

但是让我们将其概括为一个循环,类似于 Object.assign(因此得名 assignPropertyDescriptors):

function assignPropertyDescriptors(target, ...updates) {
    for (const update of updates) {
        Object.defineProperties(
            target,
            Object.getOwnPropertyDescriptors(update)
        );
    }
    return target;
}

withFlyingwithWalking 将使用该辅助函数,例如:

function withFlying(o) {
    let _isFlying = false;
    return assignPropertyDescriptors({}, o, {
        fly () {
          _isFlying = true;
        },
        land () {
          _isFlying = false;
        },
        get isFlying () {
          return _isFlying
        }
    });
};

这是一个完整的例子:

function assignPropertyDescriptors(target, ...updates) {
    for (const update of updates) {
        Object.defineProperties(
            target,
            Object.getOwnPropertyDescriptors(update)
        );
    }
    return target;
}

function withFlying(o) {
    let _isFlying = false;
    return assignPropertyDescriptors({}, o, {
        fly () {
          _isFlying = true;
        },
        land () {
          _isFlying = false;
        },
        get isFlying () {
          return _isFlying
        }
    });
};

function withWalking(o) {
    let isWalking = false;
    return assignPropertyDescriptors({}, o, {
        startWalking() {
            isWalking = true;
            return this
        },
        stopWalking() {
            isWalking = false;
            return this
        },
        isWalking: () => isWalking
    });
}

const bird = withWalking(withFlying({}))

console.log(bird.isFlying) // _isFlying starts false
bird.fly() // should set _isFlying to true
console.log(bird.isFlying) // _isFlying is true