javascript – 我的对象没有通过引用更新,逻辑有什么问题?

我已经使用了这个bug几天了,我想我已经确定了问题区域,但我不确定它为什么不起作用.我认为它可能与
a problem with passing an object by reference有关,但如果是这种情况我不知道如何将该解决方案应用于我的情况.

基本上,我正在研究(作为一种学习经验)我自己的依赖注入实现(虽然我被告知我的结构实际上被称为AMD,我将继续使用“DI”直到我了解更多差异).所以我将简要解释一下我的代码,然后重点介绍有问题的部分.

语法:

这是我的代码应该做的,它只是非常简单的DI.

我用字符串路径创建了作用域,使用“/ scopeName / subScopeName:componentName”来选择作用域,以便代码用户可以在以简单的方式定义组件时选择作用域,使用“:”从中选择一个组件.范围.

没有接口,因为在JS中键入check非常简单.没有特殊的组件类型,例如工厂,值等,每个组件都被平等对待.

var JHTML = new Viziion('JHTML');
JHTML.addScope('/generate');

/* ... snip ... */

JHTML.addComponent('/generate:process', function(nodes) {
  /* ... snip - the code inside isn't important here - snip ..*/
}).inject(['/generate:jsonInput']);

inject函数只按照组件参数的预期顺序获取一组组件路径.

钩子是存储在hooks属性中的组件,然后有一个函数returnUserHandle,它将返回一个只包含钩子的对象,因此所有的函数都隐藏在闭包中,你可以只为可用的方法提供代码用户.

JHTML.addHook('generate', function(jsonInput, process) {
   var html = process(jsonInput);
   return html;
}).inject(['/generate:jsonInput', '/generate:process']);

var handle = JHTML.returnUserHandle();

/* HTML Generator Syntax - Client */

console.log(handle.generate());

问题:

为了直观地指向正确的对象,主对象上有一个焦点属性,我想我可以在我的不同方法中使用that.focus(这是对this.focus的引用),例如addComponent和inject来链接新函数到我的作用域模型中的正确位置,并在使用addComponent创建后或在被focusComponent方法调用之后仍然将它们引用到焦点,然后inject可以找到依赖项,并通过执行以下操作“连接”它们:

that.focus = function() {
   that.focus.apply(null, dependencies);
};

我认为这会将依赖项(一个数组)打包为一个闭包,当代码用户调用该函数时,会应用正确的依赖项,这就是球类游戏.但不,不.这些函数似乎没有通过引用从that.focus传递到范围模型中. that.focus更新,但范围模型没有.

我的参考逻辑出了什么问题?

代码:

这是代码的简化版本.我想我已经尽力解释它是如何工作的,以及我试图解决的参考问题到底在哪里.

    /* Dependency Injection Framework - viziion.js */

    function Viziion() {
      var that = this;

      //here's the focus property I mentioned
      this.focus = null;

      this.scope = {
        '/': {
          'subScopes': {},
          'components': {}
        }
      };
      this.hooks = {};

      this.addScope = function(scopeName) {
        /* the way this works inst relevant to the problem */
      };

      this.addComponent = function(componentName, func) {

        var scopeArray = // snip 
          // snip - just code to read the component path

          for (var i = 0; i <= scopeArray.length; i++) {
            if (scopeArray[i] !== "") {
              if (scope.subScopes[scopeArray[i]]) {
                scope = scope.subScopes[scopeArray[i]];
              } else if (i == scopeArray.length) {

                // And here's where I add the component to the scope model
                // and reference that component in the focus property
                scope.components[scopeName] = func;
                that.focus = scope.components[scopeName];

              } else {
                throw 'Scope path is invalid.';
              }
            }
          }
      } else {
        throw 'Path does not include a component.';
      }
      return that;
    };

    this.returnComponent = function(componentName, callback) {
      /* ... snip ... */
    };

    this.addHook = function(hookName, func) {
      /* ... snip ... */
    };

    this.inject = function(dependencyArray) {
      if (dependencyArray) {
        var dependencies = [];
        for (var i = 0; i < dependencyArray.length; i++) {
          that.returnComponent(dependencyArray[i], function(dependency) {
            dependencies.push(dependency);
          });
        }
        that.focus = function() {
          that.focus.apply(null, dependencies);
        };
        return that;
      }
    };

    /* ... snip - focusComponent - snip ... */

    /* ... snip - returnUserHandle - snip ... */

当如上所示在“语法”标题下应用时,应该生成一个带有HTML字符串的控制台日志.

相反,我得到TypeError:undefined不是一个函数,对应于行var html = process(jsonInput);.

如果你想测试完整的代码,一起来,这里是:

/* Dependency Injection Framework - viziion.js */

function Viziion(appName) {
  if (typeof appName == 'string') {
    var that = this;
    this.name = appName;
    this.focus = null;
    this.scope = {
      '/': {
        'subScopes': {},
        'components': {}
      }
    };
    this.hooks = {};

    this.addScope = function(scopeName) {
      if (typeof scopeName == 'string') {
        var scopeArray = scopeName.split('/');
        var scope = that.scope['/'];
        for (var i = 0; i < scopeArray.length; i++) {
          if (scopeArray[i] !== "") {
            if (scope.subScopes[scopeArray[i]]) {
              scope = scope.subScopes[scopeArray[i]];
            } else {
              scope.subScopes[scopeArray[i]] = {
                'subScopes': {},
                'components': {}
              };
            }
          }
        }
      } else {
        throw 'Scope path must be a string.';
      }
      return that;
    };

    this.addComponent = function(componentName, func) {
      if (typeof componentName == 'string') {
        var scopeArray = componentName.split(':');
        if (scopeArray.length == 2) {
          var scope = that.scope['/'];
          var scopeName = scopeArray[1];
          scopeArray = scopeArray[0].split('/');
          for (var i = 0; i <= scopeArray.length; i++) {
            if (scopeArray[i] !== "") {
              if (scope.subScopes[scopeArray[i]]) {
                scope = scope.subScopes[scopeArray[i]];
              } else if (i == scopeArray.length) {
                scope.components[scopeName] = func;
                that.focus = scope.components[scopeName];
              } else {
                throw 'Scope path is invalid.';
              }
            }
          }
        } else {
          throw 'Path does not include a component.';
        }
      } else {
        throw 'Component path must be a string.';
      }
      return that;
    };

    this.returnComponent = function(componentName, callback) {
      if (typeof componentName == 'string') {
        var scopeArray = componentName.split(':');
        if (scopeArray.length == 2) {
          var scope = that.scope['/'];
          var scopeName = scopeArray[1];
          scopeArray = scopeArray[0].split('/');
          for (var i = 0; i <= scopeArray.length; i++) {
            if (scopeArray[i] !== "") {
              if (i == scopeArray.length) {
                callback(scope.components[scopeName]);
              } else if (scope.subScopes[scopeArray[i]]) {
                scope = scope.subScopes[scopeArray[i]];
              } else {
                throw 'Scope path is invalid.';
              }
            }
          }
        } else {
          throw 'Path does not include a component.';
        }
      } else {
        throw 'Component path must be a string.';
      }
    };

    this.addHook = function(hookName, func) {
      if (typeof hookName == 'string') {
        that.hooks[hookName] = func;
        that.focus = that.hooks[hookName];
      } else {
        throw 'Hook name must be a string.';
      }
      return that;
    };

    this.inject = function(dependencyArray) {
      if (dependencyArray) {
        var dependencies = [];
        for (var i = 0; i < dependencyArray.length; i++) {
          that.returnComponent(dependencyArray[i], function(dependency) {
            dependencies.push(dependency);
          });
        }
        console.log(that.focus);
        that.focus = function() {
          that.focus.apply(null, dependencies);
        };
        console.log(that.focus);
        console.log(that.scope);
        return that;
      }
    };

    this.focusComponent = function(componentPath) {
      that.focus = that.returnUserHandle(componentPath);
    };

    this.returnUserHandle = function() {
      return that.hooks;
    };

  } else {
    throw 'Viziion name must be a string.';
  }
}

/* JSON HTML Generator - A Simple Library Using Viziion */

var JHTML = new Viziion('JHTML');

JHTML.addScope('/generate');

JHTML.addComponent('/generate:jsonInput', [{
  tag: '!DOCTYPEHTML'
}, {
  tag: 'html',
  children: [{
    tag: 'head',
    children: []
  }, {
    tag: 'body',
    children: []
  }]
}]);

JHTML.addComponent('/generate:process', function(nodes) {
  var html = [];
  var loop = function() {
    for (var i = 0; i < nodes.length; i++) {
      if (nodes[i].tag) {
        html.push('<' + tag + '>');
        if (nodes[i].children) {
          loop();
        }
        html.push('</' + tag + '>');
        return html;
      } else {
        throw '[JHTML] Bad syntax: Tag type is not defined on node.';
      }
    }
  };
}).inject(['/generate:jsonInput']);



JHTML.addHook('generate', function(jsonInput, process) {
  console.log('Process func arg:');
  console.log(process);
  var html = process(jsonInput);
  return html;
}).inject(['/generate:jsonInput', '/generate:process']);

var handle = JHTML.returnUserHandle();

/* HTML Generator Syntax - Client */

console.log(handle.generate());

最佳答案 大问题,更大的答案.让我们开始吧.

重型OOP,适当范围

首先,从你的代码来看,你可能还没有完全掌握这个概念.

除非您事先更改对象方​​法的执行上下文,否则所述对象的方法始终将其上下文绑定到对象实例.

那是:

function A () {
  var that = this;
  this.prop = 1;
  this.method = function () {
    console.log(that.prop);
  };
}

new A().method();

通常相当于:

function A () {
  this.prop = 1;

  this.method = function () {
    console.log(this.prop);
  };
}

new A().method();

除非在使用.bind,.call或.apply执行之前调整方法.

为什么这很重要?好吧,如果我们正确使用这个上下文,我们就可以利用对象原型.原型作为一种更优雅的解决方案,可以在每个实例的基础上定义对象的每个方法.

这里我们创建两个实例,但只有一个方法.

function A () {
  this.prop = 1;
}

A.prototype.method = function () {
  console.log(this.prop);
};

new A().method();
new A().method();

这对于清晰起见很重要,稍后在绑定函数的上下文和参数(!)时很重要.

代码卫生

如果您愿意,可以跳过此主题(请转到“问题”),因为它可能被认为是不合适的,但请记住,它确实与代码的部分问题有关.

您的代码很难阅读.

以下是对此的一些看法.

原型

使用它们.您不必担心用户更改您的执行上下文,因为这可能是对您的程序的误用.考虑到安全性源代码,安全性不应成为问题.

这里没什么好说的.

提前退出

如果您正在进行健全性检查,请尽可能在代码中尽早选择退出.如果你因为类型不匹配而需要抛出,那么就扔掉那里 – 之后不再是27行.

// Not great
if (typeof input === 'string') {
  ...
} else throw 'it away';


// Better
if (typeof input !== 'string') throw 'it away';
...

这也适用于循环 – 正确使用continue关键字.这两件事都可以提高代码清晰度,减少嵌套和代码膨胀.

循环缓存

当您循环遍历数据结构,并且计划在块中多次使用当前元素时,应将该元素保存在变量中.访问元素和属性不一定是免费的OP.

// Not great
for (var i = 0; i < myArray.length; i++) {
  if (myArray[i] > 5) callback(myArray[i]);

  internalArray.push(myArray[i]);
}

// Better
var len = myArray.length, element;

for (var i = 0; i < len; i++) {
  element = myArray[i];

  if (element > 5) callback(element);

  internalArray.push(element);
}

如果使用得当,这可以提高清晰度和性能.

问题

首先,我们在这做什么?整个问题归结为功能绑定的过于复杂的应用.也就是说,只需更改函数的执行上下文.

我还要直截了当地说这个程序没有错误 – 它只是有缺陷的.

问题的主要症结是这三条线

that.focus = function() {
  that.focus.apply(null, dependencies);
};

在注入方法中找到.他们没有任何意义.这将导致无限递归,简单明了.当你定义那个函数时,它根本就不关心它的焦点属性是什么.这仅仅在执行时才有意义.

幸运的是,我们实际上从未实现这一点,因为流程组件没有得到正确的约束.

问题的很大一部分是焦点属性.在您的程序中,您将此作为一种最近的操作.关于刚刚发生的事情的独特历史.问题是,你试图以奇怪的方式热交换这个值.

然而,由于注入的反向应用,需要焦点属性(以及稍后将看到的其他属性).将组件/钩子寄存器组织成注入模型的方式要求在方法调用之间保持状态.

作为本节的结束语,进程组件函数定义永远不会返回任何内容.即使您的模型是正确的,您的输入也是有缺陷的. handle.generate()总是会返回undefined.

答案(s)

那我们怎么解决这个问题呢?好吧,第一个想法是废弃它,老实说.在我看来,反向注射模型很难看.注入方法涉及的间接水平从表面上非常混乱.

但是,我们什么都不会学到,是吗?

真的,我们如何解决这个问题?好吧,很多令功能程序员阅读的沮丧,我们需要保持更多的状态.

就其本身而言,我们的焦点属性无法提供足够的信息来正确更改函数的执行上下文.

在我们的焦点之上,它将简单地包含对我们最新组件值的引用,我们需要字段(组件/钩子名称)和片段(组件对象,如果是钩子则没有).

在注入中使用这两个或三个值,我们可以获取我们的depedancies数组,将它绑定到我们的焦点,并将结果函数设置回我们的字段.

关于下一部分的好处是我们实际上可以通过使我们的组件的上下文/挂钩未绑定的函数来放弃我们的闭包.

整个操作如下:

var focus = this.focus,
    fragment = this.fragment,
    field = this.field,
    hook = function hook () {
      return this.apply(null, arguments);
    }, func;

dependencies.unshift(focus);
func = Function.prototype.bind.apply(hook, dependencies);

if (fragment) fragment[field] = func;
else this.hooks[field] = func;

大部分内容应该非常简单,但有一件可能会给人们带来一些问题.要记住的重要一点是,我们本质上是按顺序创建两个函数,在某种意义上“丢弃”第一个函数. (应该注意的是,这可以通过hook.bind.apply以另一种方式完成,但它会创建更令人困惑的代码.这就像你可以获得的那样优雅.)

dependencies.unshift(focus);
func = Function.prototype.bind.apply(hook, dependencies);

首先,我们将焦点(我们的原始函数)添加到依赖列表的前面.这一点很重要.

然后我们使用Function.prototype.apply调用Function.prototype.bind(记住函数原型方法也共享函数原型方法.几乎一直都是乌龟).

现在我们传递我们的绑定上下文,钩子和我们的前缀依赖项来应用.

hook用作bind的主机,其上下文由传递给apply的参数数组的第一个元素改变.展开剩余的元素以形成bind的后续参数,从而创建结果函数的绑定参数.

这不是一个非常简单的概念,所以请慢慢来.

另外需要注意的是我完全放弃了focusComponent.它的实施在上下文中没有意义.您的模型依赖于最后一次输入注入,因此您需要重新实现focusComponent作为一种简单调整焦点,字段和片段状态的方法.

一个小的子修复是过程组件功能.这里不详细介绍.您可以与原始代码进行比较和对比,差异非常明显.

JHTML.addComponent('/generate:process', function (nodes) {
  return (function build (struct, nodes) {
    var length = nodes.length, node, tag;

    for (var i = 0; i < length; i++) {
      node = nodes[i];
      tag = node.tag;

      if (!tag) throw '[JHTML] Bad syntax: Tag type is not defined on node.';

      struct.push('<' + tag + '>');

      if (node.children) {
        build(struct, node.children)
        struct.push('</' + tag + '>');
      }
    }

    return struct;
  }([], nodes));
}).inject(['/generate:jsonInput']);

代码

以下是我认为代码的固定版本.它以我认为对清晰度和性能都有用的风格编写.

/* Dependency Injection Framework - viziion.js */

function Scope () {
  this.subScopes = {};
  this.components = {};
}

function Viziion (appName) {
  if (typeof appName !== 'string') throw 'Viziion name must be a string.';

  this.name = appName;
  this.working = this.field = this.focus = null
  this.scope = { '/': new Scope() };
  this.hooks = {};
}

Viziion.prototype.addScope = function (scopeName) {
  if (typeof scopeName !== 'string') throw 'Scope path must be a string.';

  var scopeArray = scopeName.split('/'),
      scope = this.scope['/'],
      len = scopeArray.length,
      element, sub;

  for (var i = 0; i < len; i++) {
    element = scopeArray[i];
    if (element === '') continue;

    sub = scope.subScopes[element]

    if (sub) scope = sub;
    else scope.subScopes[element] = new Scope();
  }

  return this;
};

Viziion.prototype.addComponent = function (componentName, func) {
  if (typeof componentName !== 'string') throw 'Component path must be a string.';

  var scopeArray = componentName.split(':'),
      len, element, sub;
  if (scopeArray.length != 2) throw 'Path does not include a component.';

  var scope = this.scope['/'],
      scopeName = scopeArray[1];

  scopeArray = scopeArray[0].split('/');
  len = scopeArray.length;

  for (var i = 0; i <= len; i++) {
    element = scopeArray[i];
    if (element === '') continue;

    sub = scope.subScopes[element];

    if (sub) scope = sub;
    else if (i === len) {
      this.fragment = scope.components;
      this.field = scopeName;
      this.focus = scope.components[scopeName] = func;
    }
    else throw 'Scope path is invalid';
  };

  return this;
};

Viziion.prototype.returnComponent = function (componentName, callback) {
  if (typeof componentName !== 'string') throw 'Component path must be a string.';

  var scopeArray = componentName.split(':'),
      len, element, sub;
  if (scopeArray.length != 2) throw 'Path does not include a component.';

  var scope = this.scope['/'],
      scopeName = scopeArray[1];

  scopeArray = scopeArray[0].split('/');
  len = scopeArray.length;

  for (var i = 0; i <= len; i++) {
    element = scopeArray[i];
    if (element === '') continue;
    sub = scope.subScopes[element]

    if (i === len) callback(scope.components[scopeName]);
    else if (sub) scope = sub;
    else throw 'Scope path is invalid';
  }
};

Viziion.prototype.addHook = function (hook, func) {
  if (typeof hook !== 'string') throw 'Hook name must be a string.';
  this.fragment = null;
  this.field = hook;
  this.focus = this.hooks[hook] = func;
  return this;
};

Viziion.prototype.inject = function (dependancyArray) {
  if (!dependancyArray) return;

  var dependencies = [],
      len = dependancyArray.length,
      element;

  function push (dep) { dependencies.push(dep); }

  for (var i = 0; i < len; i++) {
    element = dependancyArray[i];
    this.returnComponent(element, push);
  }

  var focus = this.focus,
      fragment = this.fragment,
      field = this.field,
      hook = function hook () {
        return this.apply(null, arguments);
      }, func;

  dependencies.unshift(focus);
  func = Function.prototype.bind.apply(hook, dependencies);

  if (fragment) fragment[field] = func;
  else this.hooks[field] = func;

  return this;
};

Viziion.prototype.returnUserHandle = function () { return this.hooks; };

/* JSON HTML Generator - A Simple Library Using Viziion */

var JHTML = new Viziion('JHTML');

JHTML.addScope('/generate');

JHTML.addComponent('/generate:jsonInput', [{
  tag: '!DOCTYPE html'
}, {
  tag: 'html',
  children: [{
    tag: 'head',
    children: []
  }, {
    tag: 'body',
    children: []
  }]
}]);

JHTML.addComponent('/generate:process', function (nodes) {
  return (function build (struct, nodes) {
    var length = nodes.length, node, tag;

    for (var i = 0; i < length; i++) {
      node = nodes[i];
      tag = node.tag;

      if (!tag) throw '[JHTML] Bad syntax: Tag type is not defined on node.';

      struct.push('<' + tag + '>');

      if (node.children) {
        build(struct, node.children)
        struct.push('</' + tag + '>');
      }
    }

    return struct;
  }([], nodes));
}).inject(['/generate:jsonInput']);

JHTML.addHook('generate', function (jsonInput, process) {
  return process(jsonInput);
}).inject(['/generate:jsonInput', '/generate:process']);


var handle = JHTML.returnUserHandle();

console.log(JHTML);

/* HTML Generator Syntax - Client */

console.log(handle.generate());
点赞