我的对象不是通过引用更新,逻辑有什么问题?

时间:2015-12-12 18:37:49

标签: 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());

问题:

要直观地将inject指向正确的对象,主对象上有focus属性,我想我可以使用that.focus(这是对{{1}的引用}}在我的不同方法中,例如this.focusaddComponent,将新函数链接到我的范围模型中的正确位置,并在使用{{1}创建后仍在inject中引用它们或者在被focus方法调用之后,然后addComponent可以找到依赖关系,并通过这样做“连接”它们:

focusComponent

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

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

代码:

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

that.focus

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

相反,我得到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 ... */

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

TypeError: undefined is not a function

1 个答案:

答案 0 :(得分:2)

大问题,更大的答案。让我们开始吧。

重OOP,适当范围

首先,从您的代码来看,您可能还没有完全掌握this的概念。

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

那是:

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();

除非method在使用.bind.call.apply执行之前进行调整。

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

这里我们创建两个实例,但只有一个method

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

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

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

这对于清晰起见非常重要,以后在将上下文和参数绑定到函数(!)时非常重要。

Code Hygiene

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

您的代码难以阅读。

以下是对此的一些看法。

原型

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

这里没什么可说的。

提前退出

如果您正在进行健全性检查,请尽可能在代码中尽早退出。如果由于类型不匹配而需要throw,那么throw就在那时 - 而不是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);
}

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

问题

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

我也直截了当地说这个程序没有 bug - 它只是有缺陷。

问题的主要症结是这三行

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

inject方法中找到。他们没有任何意义。这将导致无限递归,简单明了。当您定义该功能时,它根本不关心focus that属性。这仅仅是在执行时间。

幸运的是,我们实际上从未实现过那么远,因为process组件没有被正确绑定。

问题的很大一部分是focus属性。在您的计划中,您将此作为一种最新动作使用。关于刚刚发生的事情的独特历史。问题是,你试图以奇怪的方式热交换这个值。

由于focus的反向应用,<{1}}属性(以及您稍后将看到的其他属性) 是必需的。将组件/挂钩寄存器结构化为inject模型的方式要求在方法调用之间保持状态。

作为本节的结尾注释,inject组件函数定义永远不会返回任何内容。即使您的模型是正确的,您的输入也是有缺陷的。 process本身会返回handle.generate()

答案

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

但是我们不会学到任何东西,是吗?

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

我们的inject属性本身无法提供足够的信息来正确更改我们函数的执行上下文。

focus之上,我们只需要保存对最新组件值的引用,我们需要focus(组件/钩子名称)和field(组件)对象,如果挂钩则没有。)

fragment中使用这两个或三个值,我们可以将inject数组,depedancies传递给我们的bind,并将结果函数设置回focus {1}}。

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

整个操作如下:

this

大部分内容应该非常简单,但有一件可能会给人们带来一些问题。要记住的重要一点是,我们实际上是在这里按顺序创建两个函数,&#39;丢弃&#39;在某种意义上的第一个。 (应该注意的是,这可以通过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); (我们的原始函数)添加到依赖项列表的前面。这一点很重要。

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

现在我们将绑定上下文Function.prototype.apply和我们的前缀依赖项传递给hook

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

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

另一件需要注意的事情是我完全放弃了bind。它的实现在上下文中没有意义。您的模型依赖于最后一次输入注入,因此您需要重新实施focusComponent作为一种简单调整focusComponentfocusfield的方法的状态。

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

&#13;
&#13;
process
&#13;
&#13;
&#13;

代码

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

&#13;
&#13;
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']);
&#13;
&#13;
&#13;