65.9K
CodeProject 正在变化。 阅读更多。
Home

通过 AngularJS 的 $injector.instantiate 实现通用对象实例化函数

emptyStarIconemptyStarIconemptyStarIconemptyStarIconemptyStarIcon

0/5 (0投票)

2017年6月8日

CPOL

5分钟阅读

viewsIcon

9839

本文详细介绍了 angularjs 应用程序中的对象实例化,并包含了一个通用函数实现的示例

您是否曾想过在您的 angularjs 应用程序中实例化对象?控制器、工厂、服务、装饰器甚至值——它们最终都是使用 $injector 类的实例化方法创建的,这里有一行非常有趣的、我想为您揭示的代码。

今天我将描述下一行代码

return new (Function.prototype.bind.apply(ctor, args))();

这行代码的工作原理对您来说显而易见吗?如果答案是“是”,那么感谢您的时间和耐心,希望在下一篇文章中再见。 :)

现在,当所有对 JavaScript 有经验的读者都离开了我们,我想回答我自己的问题:当我第一次看到这一行时,我感到困惑,不理解 bindapplynew() 之间这些“关系”。让我们来一起弄清楚!我建议从最后开始,也就是说:假设我们有一个带参数的构造函数,我们想实例化它的实例。

function Animal(name, sound) {
  this.name = name;
  this.sound = sound;
}

new

“有什么比这更容易的吗?”——您可能会说,而且您完全正确。

var dog = new Animal('Dog', 'Woof!'); 

如果我们想获取 Animal 构造函数的新实例,new 操作符是我们首先需要的东西。让我提供一些关于 new 操作符使用细节的信息。

引用

当代码 new Foo(...) 被执行时,会发生以下情况:

  1. 创建一个新对象,该对象继承自 Foo.prototype。
  2. 构造函数 Foo 使用指定的参数调用,并将 this 绑定到新创建的对象。new Foo 等同于 new Foo(),即如果未指定参数列表,则 Foo 在不带参数的情况下调用。
  3. 构造函数返回的对象成为整个 new 表达式的结果。如果构造函数没有显式返回对象,则使用步骤 1 中创建的对象。(通常构造函数不返回值,但如果它们想覆盖正常的对象创建过程,它们可以选择这样做。)

更多详情:https://mdn.org.cn/en/docs/Web/JavaScript/Reference/Operators/new

很好,现在我们将 Animal 构造函数的执行包装在一个函数中,使其可以通用地用于应用程序中的所有可能调用。

function CreateAnimal(name, sound) { 
  return new Animal(name, sound); 
}

过了一会儿,我们决定不仅要实例化动物,还要实例化人类(我同意您的看法,这不是最好的例子),这意味着我们至少有两种可能的方法来实现这一点:

  1. 实现一个工厂,该工厂根据所需类型自行实例化所需的构造函数实例。
  2. 通过一个参数来扩展我们当前函数的声明,该参数应该包含构造函数,并基于此构造函数返回一个带有已绑定参数的新函数(在这种情况下,bind 非常有用)。

在 $injector.instantiate 实现的情况下,选择了第二点。

bind

function Create(ctorFunc, name, sound) { 
  return new (ctorFunc.bind(null, name, sound)); 
} 

console.log( Create(Animal, 'Dog', 'Woof') ); 
console.log( Create(Human, 'Person') );

让我提供一些关于 bind 函数使用细节的信息。

引用

bind() 方法创建一个新函数,该函数在被调用时,其 this 关键字被设置为提供的值,并给定一系列参数,这些参数在调用新函数时会预置。

更多详情:https://mdn.org.cn/en/docs/Web/JavaScript/Reference/Global_Objects/Function/bind

在我们的例子中,我们将 null 作为 this 传递,因为我们将使用从 bind 函数返回的函数和 new 操作符,它会忽略现有的 this 并用一个新的空对象替换它。bind 函数调用的结果是带有已绑定参数的新函数(换句话说,return new fn; 其中 fnbind 调用结果)。

太好了,现在我们可以使用我们的 Create 函数来创建任何动物和人类,它们的构造函数……都使用 namesound 参数声明。“但并非所有动物构造函数所需的参数都将以相同的方式被人类构造函数要求”——您可能会反驳,而且您又一次完全正确。我们可以注意到两个问题:

  1. 构造函数声明可能不同或已更改(例如,参数的顺序或数量),这意味着我们必须同时在几个地方进行更改:构造函数声明、调用 Create 函数的代码行以及实例的实例化行 return new (ctorFunc.bind(null, name, sound))
  2. 我们拥有的构造函数越多,所需参数可能不同的可能性就越大,我们将无法继续使用同一个 Create 函数(否则,我们必须为每个构造函数传递所有声明的参数,但只使用必需的参数)。

apply

<meta charset="utf-8" />将参数从 Create 函数透明地直接传递到构造函数可以解决上述问题,换句话说——一个通用函数,它接受构造函数和所需的已传递构造函数参数数组,并返回一个带有已绑定参数的新函数。JavaScript 中有一个很棒的函数 apply 可以用于这种情况(或者它的类似物 call,如果我们提前知道参数的数量)。

让我简要介绍一下 apply 函数的使用细节。

引用

apply() 方法使用给定的 this 值和一个作为数组(或类数组对象)提供的参数列表来调用一个函数。

apply 与 call() 非常相似,除了它支持的参数类型。您使用参数数组而不是参数列表(参数)。

更多详情:https://mdn.org.cn/en/docs/Web/JavaScript/Reference/Global_Objects/Function/apply

<meta charset="utf-8" />我认为这里是文章中最难的部分,因为首先,我们必须像 bind 函数的 this 关键字一样,将我们的构造函数 apply(类似于 ctorFunc.bind)。其次,我们必须将右移一位的构造函数参数使用 ctorArgs.unshift(null) 传递给 bind 函数(我们记得,bind 函数的第一个参数将用作 this 函数的关键字)。

Create 内部无法访问 bind 函数,因为 window 对象被用作函数上下文,这就是为什么我们必须通过 Function.prototype 来访问它。

最后,我们得到下一个通用的实例化函数:

function Create(ctorFunc, ctorArgs) { 
  ctorArgs.unshift(null); 
  return new (Function.prototype.bind.apply(ctorFunc, ctorArgs )); 
} 

console.log( Create(Animal, ['Dog', 'Woof']) ); 
console.log( Create(Human, ['Person', 'John', 'Engineer', 'Moscow']) );

如果我们回到 angularJS,我们会注意到 AnimalHuman 正是作为工厂构造函数使用的,并且它们的参数数组由找到并解析的依赖项表示。

angular
  .module('app')
  .factory(function($scope) {
    // constructor 
  });

angular
  .module('app')
  .factory(['$scope', function($scope) {
    // constructor 
  }]);

在实现我们自己的 $injector.instantiate 方法的最后阶段,我们只需要找出实例化实例的构造函数,接收(尽可能解析)所需的参数,就这样。 :)

欢迎评论、投票,感谢您的阅读。希望在下一篇文章中再见。

© . All rights reserved.