JS 中的组合 只创建一个对象并赋值给它不要使用箭头函数,而是使用this混合方法

问题描述

我正在学习 JS 中的组合概念。下面是我的演示代码

moveBy 函数将值正确分配给 xy

但是,setFillColor 函数不会将传递的值分配给 fillColor

调用 setFillColor 函数时到底发生了什么?

const withMoveBy = (shape) => ({
  moveBy: (diffX,diffY) => {
    shape.dimensions.x += diffX;
    shape.dimensions.y += diffY;
  },});

const withSetFillColor = (shape) => ({
  setFillColor: (color) => {
    console.log(shape.fillColor);                      // 1
    shape.fillColor = color;
    shape.dimensions.fillColor = color;
    console.log(shape.fillColor);                      // 2
  },});

const shapeRectangle = (dimensions) => ({
  type: 'rectangle',fillColor: 'white',dimensions,});

const shapeCircle = (dimensions) => ({
  type: 'circle',});

const createShape = (type,dimensions) => {
  let shape = null;
  switch (type) {
    case 'rectangle': {
      shape = shapeRectangle(dimensions);
      break;
    }
    case 'circle': {
      shape = shapeCircle(dimensions);
      break;
    }
  }

  if (shape) {
    shape = {
      ...shape,...withSetFillColor(shape),...withMoveBy(shape),};
  }
  return shape;
};

let r = createShape('rectangle',{
  x: 1,y: 1,width: 10,height: 10,});

let c = createShape('circle',{ x: 10,y: 10,diameter: 10 });

r.moveBy(2,3);
c.moveBy(1,2);

r.setFillColor('red');
c.setFillColor('blue');

console.log(r);
console.log(c);

输出

标记// 1 的行在矩形和圆形的情况下打印 white

标记// 2 的线为矩形打印 red,为圆形打印 blue

最终输出为:

{
  "type": "rectangle","fillColor": "white","dimensions": {
    "x": 3,"y": 4,"width": 10,"height": 10,"fillColor": "red"
  }
}
{
  "type": "circle","dimensions": {
    "x": 11,"y": 12,"diameter": 10,"fillColor": "blue"
  }
}

作为对象属性fillColor仍然是white。 但是,dimensions 中的那个取了正确的值。

解决方法

问题源于 createShape 中的这个作业 - 我的注释:

    // creating the "new object"
    shape = {
      ...shape,// shallow copying of the "old object"
      ...withSetFillColor(shape),...withMoveBy(shape),};

在这里,您创建一个新对象,它由:

  • 现有 ...shape 的浅拷贝属性(类型、填充颜色、尺寸,这是一个对象)
  • setFillColor,一个绑定到 shape旧对象)的闭包
  • moveBy,一个绑定到 shape旧对象)的闭包

执行此语句后,您创建了两个形状:

  • 方法操作的“旧对象”
  • 返回的“新对象”

在从旧对象复制的属性中,只有维度是非原始值,因此在实例之间共享。

然后,当你打电话时:

r.moveBy(2,3);

它改变了 oldShape.dimensions,但它与 newShape.dimensions 是同一个对象,所以它在输出中可见。

但是,这个调用:

r.setFillColor('red');

修改了您没有看到的 fillColoroldShape 属性。它还写入 oldShape.dimensions.fillColor,它再次在对象之间共享,因此更改在两者中都是可见的。

,

让我通过重写您的代码来说明问题。我删除了一些细节,只关注这个问题。在代码中添加了注释和日志记录以更清楚地显示发生了什么:

const withSetFillColor = (shape) => ({
  setFillColor: (color) => {
    console.log(`now changing shape with id [${shape.id}]`);
    shape.fillColor = color;
    shape.dimensions.fillColor = color;
  },});

const shapeRectangle = (dimensions) => ({
  id: 1,//add an ID of the created object for illustrative purpose
  type: 'rectangle',fillColor: 'white',dimensions,});

const createShape = (type,dimensions) => {
  //variable is now named 1 to showcase what happens
  let shape1 = null;
  switch (type) {
    case 'rectangle': {
      shape1 = shapeRectangle(dimensions);
      break;
    }
  }
  
  //this is effectively what happens when you clone and reassign an object:
  //a *second one* is created but the first one persists
  let shape2 = null;
  if (shape1) {
    shape2 = {
      ...shape1,...withSetFillColor(shape1),id: 2,//make it a different ID for illustrative purpose
    };
  }
  
  console.log(`Created shape1 and shape2 and they are the same: ${shape1 === shape2}`);
  console.log(`The dimensions object is the same: ${shape1.dimensions === shape2.dimensions}`);
  
  return shape2;
};

let r = createShape('rectangle',{
  x: 1,y: 1,width: 10,height: 10,});

r.setFillColor('red');

console.log(r);

您创建和操作两个不同的对象。这就是为什么代码为对象分配了一个属性但它看起来好像没有改变的原因。

有几种方法可以解决这个问题。

只创建一个对象并赋值给它

如果您使用 Object.assign(),您可以直接更改一个对象而不是两个相互竞争的对象。因此,将对象传递给 withX() 函数将按预期工作。

const withMoveBy = (shape) => ({
  moveBy: (diffX,diffY) => {
    shape.dimensions.x += diffX;
    shape.dimensions.y += diffY;
  },});

const withSetFillColor = (shape) => ({
  setFillColor: (color) => {
    shape.fillColor = color;
    shape.dimensions.fillColor = color;
  },});

const shapeRectangle = (dimensions) => ({
  type: 'rectangle',});

const shapeCircle = (dimensions) => ({
  type: 'circle',dimensions) => {
  let shape = null;
  switch (type) {
    case 'rectangle': {
      shape = shapeRectangle(dimensions);
      break;
    }
    case 'circle': {
      shape = shapeCircle(dimensions);
      break;
    }
  }

  if (shape) {
    //use Object assign to only manipulate one `shape` object
    Object.assign( 
      shape,withSetFillColor(shape),withMoveBy(shape)
    );
  }
  return shape;
};

let r = createShape('rectangle',});

let c = createShape('circle',{ x: 10,y: 10,diameter: 10 });

r.moveBy(2,3);
c.moveBy(1,2);

r.setFillColor('red');
c.setFillColor('blue');

console.log(r);
console.log(c);

不要使用箭头函数,而是使用this

或者,使用常规函数或 the shorthand method definition syntax 可让您使用 this。然后,您可以将这些方法添加到您的对象中,并使用 this 来引用该对象,而不必将其传入。

const withMoveBy = { //no need for a function to produce the object
  moveBy(diffX,diffY) { //shorthand method syntax
    this.dimensions.x += diffX;
    this.dimensions.y += diffY;
  },};

const withSetFillColor = { //no need for a function to produce the object
  setFillColor(color) { //shorthand method syntax
    this.fillColor = color;
    this.dimensions.fillColor = color;
  },};

const shapeRectangle = (dimensions) => ({
  type: 'rectangle',dimensions) => {
  let shape = null;
  switch (type) {
    case 'rectangle': {
      shape = shapeRectangle(dimensions);
      break;
    }
    case 'circle': {
      shape = shapeCircle(dimensions);
      break;
    }
  }

  if (shape) {
    shape = {
      ...shape,...withSetFillColor,...withMoveBy,};
  }
  return shape;
};

let r = createShape('rectangle',2);

r.setFillColor('red');
c.setFillColor('blue');

console.log(r);
console.log(c);

混合方法

这更多是对正在发生的事情的解释,而不是实际的新方法。

以上两种方法都有效,但显示的是同一枚硬币的两面。将对象组合在一起称为mixin*。 Mixin 与对象组合相似,因为您可以从更简单的对象构建更复杂的对象,但由于您是通过串联来构建的,因此它也有自己的单独类别。

传统上,您会使用 Object.assign(obj,mixinA,mixinB) 将内容添加到 obj。这使它类似于第一种方法。但是,mixinAmixinB 将是第二种方法中的实际对象。

使用类语法,有一种有趣的替代方法可以向类中添加 mixin。我在这里添加它只是为了展示它 - 不使用类而使用常规对象是完全可以的。

const withMoveBy = Base => class extends Base { //mixin
  moveBy(diffX,diffY) { 
    this.dimensions.x += diffX;
    this.dimensions.y += diffY;
  }
};

const withSetFillColor = Base => class extends Base { //mixin
  setFillColor(color) {
    this.fillColor = color;
    this.dimensions.fillColor = color;
  }
};

class Shape {
  constructor({type,fillColor,dimensions}) {
    this.type = type;
    this.fillColor = fillColor;
    this.dimensions = dimensions;
  }
}

const shapeRectangle = (dimensions) => ({
  type: 'rectangle',dimensions) => {
  let shapeArgs = null;
  switch (type) {
    case 'rectangle': {
      shapeArgs = shapeRectangle(dimensions);
      break;
    }
    case 'circle': {
      shapeArgs = shapeCircle(dimensions);
      break;
    }
  }

  let shape = null;
  if (shapeArgs) {
    //add mixins to the Shape class
    const mixedInConstructor = withMoveBy(withSetFillColor(Shape));
    //create the enhanced class
    shape = new mixedInConstructor(shapeArgs);
  }
  return shape;
};

let r = createShape('rectangle',2);

r.setFillColor('red');
c.setFillColor('blue');

console.log(r);
console.log(c);

* 是的,标题是双关语。你现在可以笑了。

相关问答

Selenium Web驱动程序和Java。元素在(x,y)点处不可单击。其...
Python-如何使用点“。” 访问字典成员?
Java 字符串是不可变的。到底是什么意思?
Java中的“ final”关键字如何工作?(我仍然可以修改对象。...
“loop:”在Java代码中。这是什么,为什么要编译?
java.lang.ClassNotFoundException:sun.jdbc.odbc.JdbcOdbc...