Warning: file_get_contents(/data/phpspider/zhask/data//catemap/1/ssh/2.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
TypeScript不一致检查未定义--为什么我需要一个感叹号?_Typescript_Deno - Fatal编程技术网

TypeScript不一致检查未定义--为什么我需要一个感叹号?

TypeScript不一致检查未定义--为什么我需要一个感叹号?,typescript,deno,Typescript,Deno,我正在努力理解自动类型规则。在这个例子中,我 有一个带有可选参数的函数 检查它是否未定义,如果未定义,请填写一个新值 使用它 最后还回来 步骤1、2和4按预期工作。在步骤4,typescript清楚地知道“map”参数不能未定义。但在第3步,我必须明确地添加一个!否则我会收到一条错误消息 代码可以工作(现在我已经添加了感叹号/断言),但没有意义。这是TypeScript的正确行为吗?我的代码是否有问题?3和4都是指同一个变量,2是在它们之前完成的,所以我看不出有什么区别 function par

我正在努力理解自动类型规则。在这个例子中,我

  • 有一个带有可选参数的函数
  • 检查它是否未定义,如果未定义,请填写一个新值
  • 使用它
  • 最后还回来
  • 步骤1、2和4按预期工作。在步骤4,typescript清楚地知道“map”参数不能未定义。但在第3步,我必须明确地添加一个!否则我会收到一条错误消息

    代码可以工作(现在我已经添加了感叹号/断言),但没有意义。这是TypeScript的正确行为吗?我的代码是否有问题?3和4都是指同一个变量,2是在它们之前完成的,所以我看不出有什么区别

    function parseUrlArgs(inputString: string, map?: Map<string, string>) : Map<string, string> {
      if (!map) {
        map = new Map();
      }
      //map = map??new Map();  // This has the exact same effect as the if statement, above.
      // Note:  JavaScript's string split would not work the same way.  If there are more than two equals signs, String.split() would ignore the second one and everything after it.  We are using the more common interpretation that the second equals is part of the value and someone was too lazy to quote it.
      const re = /(^[^=]+)=(.*$)/;
      // Note:  trim() is important on windows.  I think I was getting a \r at the end of my lines and \r does not match ".".
      inputString.trim().split("&").forEach((kvp) => {
        const result = re.exec(kvp);
        if (result) {
          const key = decodeURIComponent(result[1]);
          const value = decodeURIComponent(result[2]);
          map!.set(key, value);  // Why do I need this exclamation mark?
        }
      });
      return map;
    }
    
    函数parseUrlArgs(inputString:string,map?:map):map{
    如果(!map){
    map=新map();
    }
    //map=map??new map();//这与上面的if语句具有完全相同的效果。
    //注意:JavaScript的字符串拆分方式不同。如果有两个以上的等号,string.split()将忽略第二个等号及其后的所有内容。我们使用的是更常见的解释,即第二个等号是值的一部分,有人懒得引用它。
    常数re=/(^[^=])+=(.*$)/;
    //注意:trim()在windows上很重要。我想我在我的行末尾得到了一个\r,并且\r与“”不匹配。
    inputString.trim().split(“&”).forEach((kvp)=>{
    const result=re.exec(kvp);
    如果(结果){
    const key=decodeURIComponent(结果[1]);
    const value=decodeURIComponent(结果[2]);
    map!.set(key,value);//为什么我需要这个感叹号?
    }
    });
    返回图;
    }
    

    我没有更改任何打字脚本设置。我正在使用Deno中内置的默认设置,如下所示。我在.

    中得到了类似的结果。typescript出现问题的原因是您正在将新映射分配给相同的
    Map
    变量。它已经确定
    map
    的类型是
    map |未定义的
    ,并且该类型始终保持不变

    简单的解决方法是在函数签名中创建新映射,这样
    Map
    就永远不会
    未定义

    function parseUrlArgs(
        inputString: string, 
        map: Map<string, string> = new Map()
    ): Map<string, string> {
    
    函数parseUrlArgs(
    inputString:string,
    map:map=newmap()
    ):地图{
    
    这里的问题是TypeScript中的一个限制:控制流分析(如中所实现和描述)不会传播到或传播到函数范围边界之外。有关详细信息和讨论,请参阅。还有一个更具体的问题,建议能够“内联”某些回调类型的控制流分析


    编译器在(!map){map=new map();}
    的情况下分析代码块
    ,并成功理解在此块之后,
    map
    肯定不是
    未定义的
    ,您可以通过尝试在该代码块之前和之后使用
    map
    的方法来演示:

    map.has(""); // error
    if (!map) {
      map = new Map();
    }
    map.has(""); // okay
    
    一切都进展顺利,直到您深入回调函数体,跨越函数范围的边界:

    [1, 2, 3].forEach(() => map.has("")); // error, map might be undefined
    
    编译器确实不知道何时或是否调用该回调。您知道数组
    forEach()
    对数组中的每个元素同步运行回调一次。但是编译器不知道这一点,甚至不知道如何在类型系统中表示这一点(没有实现某种跟踪函数如何处理回调的方法,如中所述。)

    假设您看到一个函数
    foobar(()=>map.has(“”)
    。您是否知道在没有找到
    foobar()
    的实现并对其进行检查的情况下何时或是否调用了该回调?这就是编译器所考虑的
    forEach()

    编译器认为可能会在其先前的控制流分析不再适用的某个点调用回调。“可能在外部函数的其他稍后部分中,
    map
    设置为
    undefined
    ”因此,它放弃并将
    map
    视为可能的
    undefined
    。同样,您知道情况并非如此,因为
    map
    超出了范围,而从未被
    delete
    d或对其进行
    map=undefined
    处理。但编译器不会花费必要的周期来解决这个问题。放弃是一个错误性能高于完整性的权衡


    当您意识到编译器只是假设不会在回调函数内修改闭合覆盖值时,情况会变得更糟。正如外部作用域的控制流分析不会向内传播一样,内部作用域的控制流分析也不会向外传播:

    [4, 5, 6].forEach(() => map = undefined); 
    return map; // no error?!
    
    在上面的代码中,
    映射
    在您到达
    返回映射
    时肯定会是
    未定义的
    ,但是编译器允许它,并且没有任何警告。为什么?同样,编译器不知道回调将被调用或何时调用。在关闭后丢弃所有控制流分析结果会更安全定义或调用了e,但这将使控制流分析几乎毫无用处。尝试内联回调需要了解
    forEach()
    foobar()的区别
    并且涉及大量工作,可能会导致编译器速度慢得多。假装回调不会影响控制流分析是一种折衷,在这种情况下,性能和方便性比可靠性更重要


    那么可以做些什么呢?一件简单的事情是在控制流分析发生的作用域中将您的值分配给
    const
    变量。编译器知道
    const
    变量永远不会被重新分配,并且它知道(好吧,假装)这意味着变量的类型也永远不会改变:

    function parseUrlArgs(inputString: string, map?: Map<string, string>): Map<string, string> {
      if (!map) {
        map = new Map();
      }
      const resultMap = map; // <-- const assignment here
      const re = /(^[^=]+)=(.*$)/;
      inputString.trim().split("&").forEach((kvp) => {
        const result = re.exec(kvp);
        if (result) {
          const key = decodeURIComponent(result[1]);
          const value = decodeURIComponent(result[2]);
          resultMap.set(key, value); // <-- use const variable here
        }
      });
      return resultMap; // <-- use const variable here
    }
    
    函数parseUrlArgs(inputString:string,map?:map):map{