• 【Angular专题】——(2)【译】Angular中的ForwardRef


    原文地址:https://blog.thoughtram.io/angular/2015/09/03/forward-references-in-angular-2.html

    作者:Christoph Burgdorf

    译者注:文章内容比较老,控制台信息等与新框架不完全一致,理解思路即可。

    一. 问题点在哪里

    先做一个小声明,我们现在拥有一个AppComponent,并使用DI系统向其中注入了一个NameService,因为我们使用的是Typescript,所以需要做的工作就是在构造函数的参数中声明变量nameService的类型为NameService,这样做的目的是为了向Angular提供运行时解析依赖所需要的相关信息。

    app.ts

    import { Component } from '@angular/core';
    import { NameService } from './name.service';
    
    @Component({
      selector: 'my-app',
      template: '<h1>Favourite framework: {{ name }}</h1>'
    })
    class AppComponent {
      name: string;
    
      constructor(nameService: NameService) {
        this.name = nameService.getName();
      }
    }
    

    nameService.ts

    export class NameService {
      getName () {
        return "Angular";
      }
    }
    

    上述代码是可以正常工作的,如果我们将nameService.ts中的代码直接嵌入app.ts时,会产生哪些变化呢?别着急反对,先听听我希望声明的问题点。

    import { Component } from '@angular/core';
    
    @Component({
      selector: 'my-app',
      template: '<h1>Favourite framework: {{ name }}</h1>'
    })
    class AppComponent {
      name: string;
    
      constructor(nameService: NameService) {
        this.name = nameService.getName();
      }
    }
    
    class NameService {
      getName () {
        return "Angular";
      }
    }
    

    当我们试图运行上面的代码时,它并未能够正常工作。但是在控制台上却无法得到报错信息,我猜想是因为调试Typescript代码时使用了source map。无论如何,当我们在调试器中打开Pause on caught exceptions功能时,就会在Angular框架中捕获这个错误:

    Cannot resolve all parameters for AppComponent(undefined). Make sure they all have valid type or annotations

    错误信息显示,AppComponent的构造函数在被调用时,同一个文件中声明的NameService类型的变量是undefined。这个错误提示是合理的,因为我们在定义NameService之前就在AppComponent的构造函数中使用了它,但是另一方面来看,在普通的ES5代码中就不会出现报错,因为函数声明会被Js解释器提升至作用域头部,不是说ES6仅仅是ES5的语法糖么?

    那如果我们将NameService的定义代码进行提前,会出现什么情况呢:

    import { Component } from '@angular/core';
    
    class NameService {
      getName () {
        return "Angular";
      }
    }
    
    @Component({
      selector: 'my-app',
      template: '<h1>Favourite framework: {{ name }}</h1>'
    })
    class AppComponent {
      name: string;
    
      constructor(nameService: NameService) {
        this.name = nameService.getName();
      }
    }
    

    此时它似乎可以正常工作了。那么问题来了:

    Javascript解释器进行这样的改动意义何在呢?

    二. 不对Class定义进行提升的理由

    先来理解一下Javascript语言的机制,Javascript解释器不进行类的提升,是因为变量提升会导致在使用extend关键字实现继承时会导致错误,例如当被继承者是一个合法的函数表达式时。来看这样一段ES6代码:

    class Dog extends Animal {
    
    }
    
    function Animal() {
      this.move = function () {
        alert(defaultMove);
      }
    }
    
    var defaultMove = "moving";
    
    var dog = new Dog();
    dog.move();
    

    上述代码是能够正常工作的,因为Javascript解释器对其进行了提升重组,实际相当于如下代码:

    var defaultMove, dog;
    
    function Animal() {
      this.move = function () {
        alert(defaultMove);
      }
    }
    
    class Dog extends Animal {
    
    }
    
    defaultMove = "moving";
    
    dog = new Dog();
    dog.move();
    

    然而,如果将Animal从一个函数声明改变成一个函数表达式时,它是不会被提升的。

    //将函数声明改变为函数表达式
    class Dog extends Animal {
    
    }
    
    var Animal = function Animal() {
      this.move = function () {
        alert(defaultMove);
      }
    }
    
    var defaultMove = "moving";
    
    var dog = new Dog();
    dog.move();
    

    提升后的真实执行顺序如下,函数表达式并没有被提升:

    var Animal, defaultMove, dog;
    
    class Dog extends Animal {
    
    }
    
    Animal = function Animal() {
      this.move = function () {
        alert(defaultMove);
      }
    }
    
    defaultMove = "moving";
    
    dog = new Dog();
    dog.move();
    

    如果函数表达式也被提升,那么当Dog类继承Animal类时就会报错,因为它还没有被声明。从上面的示例中不难看出,如果Javascript解释器对class声明也进行提升处理,就容易在类继承时出现基类未定义的错误。

    三. class在使用前必须声明吗?

    我们理解了class为什么不适合被提升执行顺序,这对于之前的Angular的示例来说有什么指导意义呢?我们只能通过将NameService移动到代码顶部的方式来解除之前的报错吗?

    答案是我们可以使用另一种解决方案。我们使用@Inject注解和forwardRef函数来替代之前方式,也就是声明一个NameService类型的参数nameService,如下所示:

    import { Component, Inject, forwardRef } from '@angular/core';
    
    @Component({
        selector:'my-app',
        template:'<h1>Favourite framework:{{ name }}</h1>'
    })
    class AppComponent{
        name: string;
        
        constructor(@Inject(forwardRef(()=> NameService)) nameService){
            this.name = nameService.getName();
        }
    }
    
    class NameService{
        getName(){
            return "Angular"
        }
    }
    

    forwardRef所做的工作,就是接收一个函数作为参数,然后返回一个class,因为这个函数并不是立即被调用的,而是在NameService声明之后才会安全地返回NameService,也就是说当()=>NameService执行的时候,NameService的值已经不是undefined了。

    四. 小结

    这个场景并不会经常出现,一般它只在当我们想要注入在同一个文件中声明的类时才会发生,大多数情况下我们在一个文件中只会声明一个类,并且会在文件的头部引入其他依赖的类,以此来保证不会被class不进行变量提升的特性造成困扰。

    五.补充

    以下内容摘录自Angular中文网:

    在Typescript里面,类声明的顺序很重要,如果一个类尚未定义,就不能引用它。

    这通常都没有问题的,特别是遵循一个文件一个类规则的时候。但有时候循环引用可能无法避免,当类A引用类B,同时B又引用A时,就会陷入困境:它们中的某一个必须先定义。

    forwardRef( )建立一个间接引用,供Angular随后解析。

  • 相关阅读:
    selenium 设置等待时间
    mac下配置python+selenium+chrome环境
    自定义filter
    urllib登录的cookie复制到headers,模拟登录人人网
    urllib中的保存cookie使用,运用cookiejar来模拟登录人人网
    urllib中的cookie使用,四种方法
    urllib中的https使用,导入ssl模块
    urllib判断重定向
    urllib中的down,下载百度图片为例
    urllib中的本地代理设置
  • 原文地址:https://www.cnblogs.com/dashnowords/p/10123696.html
Copyright © 2020-2023  润新知