精华内容
下载资源
问答
  • undefined
    千次阅读
    2021-10-29 08:55:01

    目录

    一、前言

    二、undefined的特点

    1、undefined既是JavaScript中的原始数据类型之一,也是一个原始值数据

    2、undefined是全局对象上的一个属性

    3、undefined不可删,不可重新定义,不可枚举

    (1)undefined不可删除

    (2)undefined不可重新定义

    (3)undefined不可枚举

    4、undefined不是JavaScript的关键字和保留字,

    二、什么情况下会出现undefined

    1、已声明但未被初始化的变量默认赋值undefined

    2、未被声明的变量使用typeof操作符检测数据类型时返回undefined

     3、当函数没有明确指定返回值时,默认返回undefined

     4、viod()返回undefined

    三、undefined在类型转换中的结果

    四、总结


    一、前言

    在前端的实际开发工作中,经常会遇到undefined。在求职面试的过程中,undefined也是频繁出现。作为web前端开发人员,无论是实际的工作还是求职面试。掌握undefined这个知识点都是很有必要的。接下来简单说一下我对JavaScript中undefined的理解

    二、undefined的特点

    1、undefined既是JavaScript中的原始数据类型之一,也是一个原始值数据

    对于有编程经验的朋友来说,undefined是JavaScript的原始数据类型之一这一点毋庸置疑,没有什么可以赘述的,因为ECMAScript规范中就将undefined定义为原始数据类型之一。

    undefined也是一个原始值数据,我的理解是,当一个声明了但是未初始化的变量会默认赋值为字符串“undefined”

        <script>
            let num;
            console.log(num)//undefined
        </script>

    2、undefined是全局对象上的一个属性

    当我们直接输出全局window对象时,就可以找到undefined的存在,当然也可以直接输出window.undefined得到undefined

        <script>
           console.log(window);
           console.log(window.undefined);//undefined
        </script>

    3、undefined不可删,不可重新定义,不可枚举

    (1)undefined不可删除

    以下操作虽然使用delete操作想将undefined删除,但是达不到效果,使用delete操作后,再次输出undefined依然可行

        <script>
           delete window.undefined;
           console.log(undefined);//undefined
        </script>

    (2)undefined不可重新定义

    以下操作会报错

     <script>
            Object.defineProperty(window,'undefined',{
                enumerable:true,
                writable:true,
                configurable:true
            });
        </script>

    (3)undefined不可枚举

    既然undefined是全局对象window中的一个属性,那么是否可以通过循环遍历的方式将undefined从window对象中遍历出来呢?结果很遗憾,不可以。

    以下代码不会输出任何内容

      <script>
            for(k in window){
               if(k===undefined){
                   console.log(key)//不会输出内容
               }
           }
        </script>

    4、undefined不是JavaScript的关键字和保留字,

    undefined不是JavaScript的关键字和保留字在全局作用域中不重新赋值,但是在函数作用域中可以重新赋值,但是不建议这么做,不建议将undefined作为变量、对象、函数等名称使用,

    在全局作用域中,undefined不可以重新赋值,即使重新赋值了,但是无效,在全局中输出的依然是undefined

      <script>
          window.undefined=1;
          console.log(window.undefined);//undefined
      </script>

    但是在局部作用域中,可以将undefined作为变量名重新赋值,但是不建议这么做。

    以下fun函数执行输出结果为2

     <script>
        function fun(){
            let undefined=2;
            console.log(undefined)//2
        }
        fun();
      </script>

    二、什么情况下会出现undefined

    1、已声明但未被初始化的变量默认赋值undefined

      <script>
          let a;
          console.log(a);//undefined
      </script>

    2、未被声明的变量使用typeof操作符检测数据类型时返回undefined

      <script>
          console.log(typeof a)//undefined
      </script>

     3、当函数没有明确指定返回值时,默认返回undefined

      <script>
          function fun(){
              console.log(333);
          }
         console.log(fun());//函数默认会返回一个undefined
      </script>

    当函数有明确的返回值时,则返回指定的值

      <script>
          function fun(){
              console.log(333);
              return "666666"//指定函数返回值,则不再返回undefined
          }
         console.log(fun());
      </script>

     4、viod()返回undefined

    使用viod操作任何值返回的都是undefined

    以下代码均输出undefined

      <script>
         console.log(void(122));//undefined
         console.log(void(0));//undefined
         console.log(void(3));//undefined
         console.log(void("南山"));//undefined
      </script>

    因此在一些比较久远的代码中,偶尔会看到这样的代码

        <a href="javascript:void(0)"></a>//阻止超链接跳转

     viod获取到的undefined其实就是全局对象window中的undefined。比如:viod(0)===window.undefined

      <script>
         console.log(void(0)===window.undefined)//true
         console.log(void(0)===undefined);//true
      </script>

    在局部作用域中,如果使用undefined作为变量名并重新赋值,一定要注意,此时的void(0)不一定等于undefined。

    通过viod得到的undefined是全局对象window中的undefined。只和全局中的undefined相等。不一定等于局部作用域中重新赋值的undefined

    <script>
         function fun(){
             let undefined=2;
             console.log(undefined===void(0));//false
             if(void(0)===undefined){//条件不成立
                 console.log(undefined);//不会输出内容
             }
         }
         fun();
      </script>

    三、undefined在类型转换中的结果

    undefined是有个假值false。但是在做类型转换的时候,要根据具体的情况来分析

    1. 在使用Number(),parseInt(),parseFloat(),+,- 运算符进行类型转换时,会转换成NaN。
    2. 在使用!运算符转换时会转换成true
    3. undefined==null
    4. undefined不等于0
    5. undefined 没有toString方法,将其转换成字符串是,不能使用toString方法,但是可以使用String()方法。
      <script>
         console.log(Number(undefined));//NaN
         console.log(parseInt(undefined));//NaN
         console.log(parseFloat(undefined));//NaN
         console.log(+undefined);//NaN
         console.log(-undefined);//NaN
         console.log(undefined==null);//true
         console.log(!undefined);//true
         console.log(undefined==0);//false
      </script>

     在if流程控制语句中,会将undefined默认转换成对应的 Boolean值

      <script>
         let num;
         if(num){
             console("3333")//不会输出
         }
    
         if(!num){
             console.log(9999)//999
         }
      </script>

    四、总结

    以上几点是我对undefined的总结,以后遇见新的undefined的知识点也会在该文章中继续补充。但是学海无涯,虽然我尽可能的将自己理解的表达正确和全面,但是难免会有疏漏,若有遗漏和错误,欢迎指正补充。共同交流学习,也是一种进步的途径。

    更多相关内容
  • 处理 JS中 undefined 的 7 个技巧

    千次阅读 2021-03-15 01:18:42
    摘要: JS的大部分报错都是undefined...Fundebug经授权转载,版权归原作者所有。大约8年前,当原作者开始学习JS时,遇到了一个奇怪的情况,既存在undefined 的值,也存在表示空值的null。它们之间的明显区别是什么?...

    摘要: JS的大部分报错都是undefined...

    Fundebug经授权转载,版权归原作者所有。

    大约8年前,当原作者开始学习JS时,遇到了一个奇怪的情况,既存在undefined 的值,也存在表示空值的null。它们之间的明显区别是什么?它们似乎都定义了空值,而且,比较null == undefined的计算结果为true。

    大多数现代语言,如Ruby、Python或Java都有一个空值(nil或null),这似乎是一种合理的方式。

    对于JavaScript,解释器在访问尚未初始化的变量或对象属性时返回undefined。例如:

    let company;

    company; // => undefined

    let person = { name: 'John Smith' };

    person.age; // => undefined

    另一方面,null表示缺少的对象引用,JS本身不会将变量或对象属性设置为null。

    一些原生方法,比如String.prototype.match(),可以返回null来表示丢失的对象。看看下面的示例:

    let array = null;

    array; // => null

    let movie = { name: "Starship Troopers", musicBy: null };

    movie.musicBy; // => null

    "abc".match(/[0-9]/); // => null

    由于 JS 的宽容特性,开发人员很容易访问未初始化的值,我也犯了这样的错误。

    通常,这种危险的操作会生成undefined 的相关错误,从而快速地结束脚本。相关的常见错误消息有:

    TypeError: 'undefined' is not a function

    TypeError: Cannot read property '' of undefined

    type errors

    JS 开发人员可以理解这个笑话的讽刺:

    function undefined() {

    // problem solved

    }

    为了降低此类错误的风险,必须理解生成undefined的情况。更重要的是抑制它的出现并阻止在应用程序中传播,从而提高代码的持久性。

    让咱们详细讨论undefined 及其对代码安全性的影响。

    1. undefined 是什么鬼

    JS 有6种基本类型

    Boolean: true 或 false

    Number: 1, 6.7, 0xFF

    String: "Gorilla and banana"

    Symbol: Symbol("name") (starting ES2015)

    Null: null

    Undefined: undefined.

    和一个单独的Object 类型:{name: "Dmitri"}, ["apple", "orange"]。

    根据ECMAScript规范,从6种原始类型中,undefined是一个特殊的值,它有自己的Undefined类型。

    未为变量赋值时默认值为undefined。

    该标准明确定义,当访问未初始化的变量、不存在的对象属性、不存在的数组元素等时,将接收到一个undefined 的值。例如

    let number;

    number; // => undefined

    let movie = { name: "Interstellar" };

    movie.year; // => undefined

    let movies = ["Interstellar", "Alexander"];

    movies[3]; // => undefined

    上述代码大致流程:

    未初始化的变量number

    一个不存在的对象属性movie.year

    或者不存在数组元素movies[3]

    都会被定义为undefined。

    ECMAScript规范定义了undefined 值的类型

    Undefined type是其唯一值为undefined 值的类型。

    在这个意义上,typeof undefined返回“undefined”字符串

    typeof undefined === "undefined"; // => true

    当然typeof可以很好地验证变量是否包含undefined的值

    let nothing;

    typeof nothing === "undefined"; // => true

    2. 导致undefined的常见场景

    2.1 未初始化变量

    尚未赋值(未初始化)的声明变量默认为undefined。

    let myVariable;

    myVariable; // => undefined

    myVariable已声明,但尚未赋值,默认值为undefined。

    解决未初始化变量问题的有效方法是尽可能分配初始值。 变量在未初始化状态中越少越好。 理想情况下,你可以在声明const myVariable ='Initial value'之后立即指定一个值,但这并不总是可行的。

    技巧1:使用 let 和 const 来代替 var

    在我看来,ES6 最好的特性之一是使用const和let声明变量的新方法。const和let具有块作用域(与旧的函数作用域var相反),在声明行之前都存在于暂时性死区。

    当变量一次性且永久地接收到一个值时,建议使用const声明,它创建一个不可变的绑定。

    const的一个很好的特性是必须为变量const myVariable ='initial'分配一个初始值。 变量未暴露给未初始化状态,并且访问undefined是不可能的。

    以下示例检查验证一个单词是否是回文的函数:

    function isPalindrome(word) {

    const length = word.length;

    const half = Math.floor(length / 2);

    for (let index = 0; index < half; index++) {

    if (word[index] !== word[length - index - 1]) {

    return false;

    }

    }

    return true;

    }

    isPalindrome("madam"); // => true

    isPalindrome("hello"); // => false

    length 和 half 变量被赋值一次。将它们声明为const似乎是合理的,因为这些变量不会改变。

    如果需要重新绑定变量(即多次赋值),请应用let声明。只要可能,立即为它赋一个初值,例如,let index = 0。

    那么使用 var 声明呢,相对于ES6,建议是完全停止使用它。

    fcf4c0313fbe358a7a21aa8067556af1.png

    var 声明的变量提会被提升到整个函数作用域顶部。可以在函数作用域末尾的某个地方声明var变量,但是仍然可以在声明之前访问它:对应变量的值是 undefined。

    相反,用let 或者 const 声明的变量之前不能访问该变量。之所以会发生这种情况,是因为变量在声明之前处于暂时死区。这很好,因为这样就很少有机会访问到 undefined 值。

    使用let(而不是var)更新的上述示例会引发ReferenceError 错误,因为无法访问暂时死区中的变量。

    function bigFunction() {

    // code...

    myVariable; // => Throws 'ReferenceError: myVariable is not defined'

    // code...

    let myVariable = 'Initial value';

    // code...

    myVariable; // => 'Initial value'

    }

    bigFunction();

    技巧2:增加内聚性

    内聚描述模块的元素(命名空间、类、方法、代码块)内聚在一起的程度。凝聚力的测量通常被称为高凝聚力或低内聚。

    高内聚是优选的,因为它建议设计模块的元素以仅关注单个任务,它构成了一个模块。

    专注且易懂:更容易理解模块的功能

    可维护且更容易重构:模块中的更改会影响更少的模块

    可重用:专注于单个任务,使模块更易于重用

    可测试:可以更轻松地测试专注于单个任务的模块

    d887fe0fcbc124d33b86871a1189631f.png

    高内聚和低耦合是一个设计良好的系统的特征。

    代码块本身可能被视为一个小模块,为了尽可能实现高内聚,需要使变量尽可能接近使用它们代码块位置。

    例如,如果一个变量仅存在以形成块作用域内,不要将此变量公开给外部块作用域,因为外部块不应该关心此变量。

    不必要地延长变量生命周期的一个典型例子是函数中for循环的使用:

    function someFunc(array) {

    var index, item, length = array.length;

    // some code...

    // some code...

    for (index = 0; index < length; index++) {

    item = array[index];

    // some code...

    }

    return 'some result';

    }

    index,item和length变量在函数体的开头声明,但是,它们仅在最后使用,那么这种方式有什么问题呢?

    从顶部的声明到for语句中变量 index 和 item 都是未初始化的,值为 undefined。它们在整个函数作用域内具有不合理较长的生命周期。

    一种更好的方法是将这些变量尽可能地移动到使用它们的位置:

    function someFunc(array) {

    // some code...

    // some code...

    const length = array.length;

    for (let index = 0; index < length; index++) {

    const item = array[index];

    // some

    }

    return 'some result';

    }

    index和item变量仅存在于for语句的作用域内,for 之外没有任何意义。length变量也被声明为接近其使用它的位置。

    为什么修改后的版本优于初始版本? 主要有几点:

    变量未暴露undefined状态,因此没有访问undefined的风险

    将变量尽可能地移动到它们的使用位置会增加代码的可读性

    高内聚的代码块在必要时更容易重构并提取到单独的函数中

    2.2 访问不存在的属性

    访问不存在的对象属性时,JS 返回undefined。

    咱们用一个例子来说明这一点:

    let favoriteMovie = {

    title: 'Blade Runner'

    };

    favoriteMovie.actors; // => undefined

    favoriteMovie是一个具有单个属性 title 的对象。 使用属性访问器favoriteMovie.actors访问不存在的属性actors将被计算为undefined。

    本身访问不存在的属性不会引发错误, 但尝试从不存在的属性值中获取数据时就会出现问题。 常见的的错误是 TypeError: Cannot read property of undefined。

    稍微修改前面的代码片段来说明TypeError throw:

    let favoriteMovie = {

    title: 'Blade Runner'

    };

    favoriteMovie.actors[0];

    // TypeError: Cannot read property '0' of undefined

    favoriteMovie没有属性actors,所以favoriteMovie.actors的值 undefined。因此,使用表达式favoriteMovie.actors[0]访问undefined值的第一项会引发TypeError。

    JS 允许访问不存在的属性,这种允许访问的特性容易引起混淆:可能设置了属性,也可能没有设置属性,绕过这个问题的理想方法是限制对象始终定义它所持有的属性。

    不幸的是,咱们常常无法控制对象。在不同的场景中,这些对象可能具有不同的属性集,因此,必须手动处理所有这些场景:

    接着我们实现一个函数append(array, toAppend),它的主要功能在数组的开头和/或末尾添加新的元素。 toAppend参数接受具有属性的对象:

    first:元素插入数组的开头

    last:元素在数组末尾插入。

    函数返回一个新的数组实例,而不改变原始数组(即它是一个纯函数)。

    append()的第一个版本看起来比较简单,如下所示:

    function append(array, toAppend) {

    const arrayCopy = array.slice();

    if (toAppend.first) {

    arrayCopy.unshift(toAppend.first);

    }

    if (toAppend.last) {

    arrayCopy.push(toAppend.last);

    }

    return arrayCopy;

    }

    append([2, 3, 4], { first: 1, last: 5 }); // => [1, 2, 3, 4, 5]

    append(['Hello'], { last: 'World' }); // => ['Hello', 'World']

    append([8, 16], { first: 4 }); // => [4, 8, 16]

    由于toAppend对象可以省略first或last属性,因此必须验证toAppend中是否存在这些属性。如果属性不存在,则属性访问器值为undefined。

    检查first或last属性是否是undefined,在条件为 if(toappendix .first){}和if(toappendix .last){}中进行验证:

    这种方法有一个缺点, undefined,false,null,0,NaN和''是虚值。

    在append() 的当前实现中,该函数不允许插入虚值元素:

    append([10], { first: 0, last: false }); // => [10]

    0和false是虚值的。 因为 if(toAppend.first){}和if(toAppend.last){}实际上与falsy进行比较,所以这些元素不会插入到数组中,该函数返回初始数组[10]而不会进行任何修改。

    以下技巧解释了如何正确检查属性的存在。

    技巧3: 检查属性是否存在

    JS 提供了许多方法来确定对象是否具有特定属性:

    obj.prop!== undefined:直接与undefined进行比较

    typeof obj.prop!=='undefined':验证属性值类型

    obj.hasOwnProperty('prop'):验证对象是否具有自己的属性

    'prop' in obj:验证对象是否具有自己的属性或继承属性

    我的建议是使用 in 操作符,它的语法短小精悍。in操作符的存在表明一个明确的意图,即检查对象是否具有特定的属性,而不访问实际的属性值。

    80466f0366e24a993a228b90b671b291.png

    obj.hasOwnProperty('prop')也是一个很好的解决方案,它比 in 操作符稍长,仅在对象自己的属性中进行验证。

    涉及与undefined进行比较剩下的两种方式可能有效,但在我看来,obj.prop!== undefined和typeof obj.prop!=='undefined'看起来冗长而怪异,并暴露出直接处理undefined的可疑路径。。

    让咱们使用in操作符改进append(array, toAppend) 函数:

    function append(array, toAppend) {

    const arrayCopy = array.slice();

    if ('first' in toAppend) {

    arrayCopy.unshift(toAppend.first);

    }

    if ('last' in toAppend) {

    arrayCopy.push(toAppend.last);

    }

    return arrayCopy;

    }

    append([2, 3, 4], { first: 1, last: 5 }); // => [1, 2, 3, 4, 5]

    append([10], { first: 0, last: false }); // => [0, 10, false]

    'first' in toAppend (和'last' in toAppend)在对应属性存在时为true,否则为false。in操作符的使用解决了插入虚值元素0和false的问题。现在,在[10]的开头和结尾添加这些元素将产生预期的结果[0,10,false]。

    技巧4:解构访问对象属性

    在访问对象属性时,如果属性不存在,有时需要指示默认值。可以使用in和三元运算符来实现这一点。

    const object = { };

    const prop = 'prop' in object ? object.prop : 'default';

    prop; // => 'default'

    当要检查的属性数量增加时,三元运算符语法的使用变得令人生畏。对于每个属性,都必须创建新的代码行来处理默认值,这就增加了一堵难看的墙,里面都是外观相似的三元运算符。

    为了使用更优雅的方法,可以使用 ES6 对象的解构。

    对象解构允许将对象属性值直接提取到变量中,并在属性不存在时设置默认值,避免直接处理undefined的方便语法。

    实际上,属性提取现在看起来简短而有意义:

    const object = { };

    const { prop = 'default' } = object;

    prop; // => 'default'

    要查看实际操作中的内容,让我们定义一个将字符串包装在引号中的有用函数。quote(subject, config)接受第一个参数作为要包装的字符串。 第二个参数config是一个具有以下属性的对象:

    char:包装的字符,例如 '(单引号)或“(双引号),默认为”。

    skipIfQuoted:如果字符串已被引用则跳过引用的布尔值,默认为true。

    使用对象析构的优点,让咱们实现quote()

    function quote(str, config) {

    const { char = '"', skipIfQuoted = true } = config;

    const length = str.length;

    if (skipIfQuoted

    && str[0] === char

    && str[length - 1] === char) {

    return str;

    }

    return char + str + char;

    }

    quote('Hello World', { char: '*' }); // => '*Hello World*'

    quote('"Welcome"', { skipIfQuoted: true }); // => '"Welcome"'

    const {char = '", skipifquote = true} = config解构赋值在一行中从config对象中提取char和skipifquote属性。如果config对象中有一些属性不可用,那么解构赋值将设置默认值:char为'"',skipifquote为false。

    该功能仍有改进的空间。让我们将解构赋值直接移动到参数部分。并为config参数设置一个默认值(空对象{}),以便在默认设置足够时跳过第二个参数。

    function quote(str, { char = '"', skipIfQuoted = true } = {}) {

    const length = str.length;

    if (skipIfQuoted

    && str[0] === char

    && str[length - 1] === char) {

    return str;

    }

    return char + str + char;

    }

    quote('Hello World', { char: '*' }); // => '*Hello World*'

    quote('Sunny day'); // => '"Sunny day"'

    注意,解构赋值替换了函数 config 参数。我喜欢这样:quote()缩短了一行。

    ={}在解构赋值的右侧,确保在完全没有指定第二个参数的情况下使用空对象。

    对象解构是一个强大的功能,可以有效地处理从对象中提取属性。 我喜欢在被访问属性不存在时指定要返回的默认值的可能性。因为这样可以避免undefined以及与处理它相关的问题。

    技巧5: 用默认属性填充对象

    如果不需要像解构赋值那样为每个属性创建变量,那么丢失某些属性的对象可以用默认值填充。

    ES6 Object.assign(target,source1,source2,...)将所有可枚举的自有属性的值从一个或多个源对象复制到目标对象中,该函数返回目标对象。

    例如,需要访问unsafeOptions对象的属性,该对象并不总是包含其完整的属性集。

    为了避免从unsafeOptions访问不存在的属性,让我们做一些调整:

    定义包含默认属性值的defaults对象

    调用Object.assign({},defaults,unsafeOptions)来构建新的对象options。 新对象从unsafeOptions接收所有属性,但缺少的属性从defaults对象获取。

    const unsafeOptions = {

    fontSize: 18

    };

    const defaults = {

    fontSize: 16,

    color: 'black'

    };

    const options = Object.assign({}, defaults, unsafeOptions);

    options.fontSize; // => 18

    options.color; // => 'black'

    unsafeOptions仅包含fontSize属性。 defaults对象定义属性fontSize和color的默认值。

    Object.assign() 将第一个参数作为目标对象{}。 目标对象从unsafeOptions源对象接收fontSize属性的值。 并且人defaults对象的获取color属性值,因为unsafeOptions不包含color属性。

    枚举源对象的顺序很重要:后面的源对象属性会覆盖前面的源对象属性。

    现在可以安全地访问options对象的任何属性,包括options.color在最初的unsafeOptions中是不可用的。

    还有一种简单的方法就是使用ES6中展开运算符:

    const unsafeOptions = {

    fontSize: 18

    };

    const defaults = {

    fontSize: 16,

    color: 'black'

    };

    const options = {

    ...defaults,

    ...unsafeOptions

    };

    options.fontSize; // => 18

    options.color; // => 'black'

    对象初始值设定项从defaults和unsafeOptions源对象扩展属性。 指定源对象的顺序很重要,后面的源对象属性会覆盖前面的源对象。

    使用默认属性值填充不完整的对象是使代码安全且持久的有效策略。无论哪种情况,对象总是包含完整的属性集:并且无法生成undefined的属性。

    2.3 函数参数

    函数参数隐式默认为undefined。

    通常,用特定数量的参数定义的函数应该用相同数量的参数调用。在这种情况下,参数得到期望的值

    function multiply(a, b) {

    a; // => 5

    b; // => 3

    return a * b;

    }

    multiply(5, 3); // => 15

    调用multiply(5,3)使参数a和b接收相应的5和3值,返回结果:5 * 3 = 15。

    在调用时省略参数会发生什么?

    function multiply(a, b) {

    a; // => 5

    b; // => undefined

    return a * b;

    }

    multiply(5); // => NaN

    函数multiply(a, b){}由两个参数a和b定义。调用multiply(5)用一个参数执行:结果一个参数是5,但是b参数是undefined。

    技巧6: 使用默认参数值

    有时函数不需要调用的完整参数集,可以简单地为没有值的参数设置默认值。

    回顾前面的例子,让我们做一个改进,如果b参数未定义,则为其分配默认值2:

    function multiply(a, b) {

    if (b === undefined) {

    b = 2;

    }

    a; // => 5

    b; // => 2

    return a * b;

    }

    multiply(5); // => 10

    虽然所提供的分配默认值的方法有效,但不建议直接与undefined值进行比较。它很冗长,看起来像一个hack .

    这里可以使用 ES6 的默认值:

    function multiply(a, b = 2) {

    a; // => 5

    b; // => 2

    return a * b;

    }

    multiply(5); // => 10

    multiply(5, undefined); // => 10

    2.4 函数返回值

    隐式地,没有return语句,JS 函数返回undefined。

    在JS中,没有任何return语句的函数隐式返回undefined:

    function square(x) {

    const res = x * x;

    }

    square(2); // => undefined

    square() 函数没有返回计算结果,函数调用时的结果undefined。

    当return语句后面没有表达式时,默认返回 undefined。

    function square(x) {

    const res = x * x;

    return;

    }

    square(2); // => undefined

    return; 语句被执行,但它不返回任何表达式,调用结果也是undefined。

    function square(x) {

    const res = x * x;

    return res;

    }

    square(2); // => 4

    技巧7: 不要相信自动插入分号

    JS 中的以下语句列表必须以分号(;)结尾:

    空语句

    let,const,var,import,export声明

    表达语句

    debugger 语句

    continue 语句,break 语句

    throw 语句

    return 语句

    如果使用上述声明之一,请尽量务必在结尾处指明分号:

    function getNum() {

    let num = 1;

    return num;

    }

    getNum(); // => 1

    let 声明和return 语句结束时,强制性写分号。

    当你不想写这些分号时会发生什么? 例如,咱们想要减小源文件的大小。

    在这种情况下,ECMAScript 提供自动分号插入(ASI)机制,为你插入缺少的分号。

    ASI 的帮助下,可以从上一个示例中删除分号:

    function getNum() {

    // Notice that semicolons are missing

    let num = 1

    return num

    }

    getNum() // => 1

    上面的代码是有效的JS代码,缺少的分号ASI会自动为我们插入。

    乍一看,它看起来很 nice。 ASI 机制允许你少写不必要的分号,可以使JS代码更小,更易于阅读。

    ASI 创建了一个小而烦人的陷阱。 当换行符位于return和return \n expression之间时,ASI 会在换行符之前自动插入分号(return; \n expression)。

    函数内部return; ? 即该函数返回undefined。 如果你不详细了解ASI的机制,则意外返回的undefined会产生意想不到的问题。

    来 getPrimeNumbers()调用返回的值:

    function getPrimeNumbers() {

    return

    [ 2, 3, 5, 7, 11, 13, 17 ]

    }

    getPrimeNumbers() // => undefined

    在return语句和数组之间存在一个换行,JS 在return后自动插入分号,解释代码如下:

    function getPrimeNumbers() {

    return;

    [ 2, 3, 5, 7, 11, 13, 17 ];

    }

    getPrimeNumbers(); // => undefined

    return; 使函数getPrimeNumbers() 返回undefined而不是期望的数组。

    这个问题通过删除return和数组文字之间的换行来解决:

    function getPrimeNumbers() {

    return [

    2, 3, 5, 7, 11, 13, 17

    ];

    }

    getPrimeNumbers(); // => [2, 3, 5, 7, 11, 13, 17]

    我的建议是研究自动分号插入的确切方式,以避免这种情况。

    当然,永远不要在return和返回的表达式之间放置换行符。

    2.5 void 操作符

    void 计算表达式无论计算结果如何都返回undefined 。

    void 1; // => undefined

    void (false); // => undefined

    void {name: 'John Smith'}; // => undefined

    void Math.min(1, 3); // => undefined

    void操作符的一个用例是将表达式求值限制为undefined,这依赖于求值的一些副作用。

    3. 未定义的数组

    访问越界索引的数组元素时,会得到undefined 。

    const colors = ['blue', 'white', 'red'];

    colors[5]; // => undefined

    colors[-1]; // => undefined

    colors数组有3个元素,因此有效索引为0,1和2。

    因为索引5和-1没有数组元素,所以访问colors[5]和colors[-1]值为undefined。

    JS 中,可能会遇到所谓的稀疏数组。这些数组是有间隙的数组,也就是说,在某些索引中,没有定义元素。

    当在稀疏数组中访问间隙(也称为空槽)时,也会得到一个undefined。

    下面的示例生成稀疏数组并尝试访问它们的空槽

    const sparse1 = new Array(3);

    sparse1; // => [, , ]

    sparse1[0]; // => undefined

    sparse1[1]; // => undefined

    const sparse2 = ['white', ,'blue']

    sparse2; // => ['white', , 'blue']

    sparse2[1]; // => undefined

    使用数组时,为了避免获取undefined,请确保使用有效的数组索引并避免创建稀疏数组。

    4. undefined和null之间的区别

    一个合理的问题出现了:undefined和null之间的主要区别是什么?这两个特殊值都表示为空状态。

    主要区别在于undefined表示尚未初始化的变量的值,null表示故意不存在对象。

    让咱们通过一些例子来探讨它们之间的区别。

    number 定义了但没有赋值。

    let number;

    number; // => undefined

    number 变量未定义,这清楚地表明未初始化的变量。

    当访问不存在的对象属性时,也会发生相同的未初始化概念

    const obj = { firstName: 'Dmitri' };

    obj.lastName; // => undefined

    因为obj中不存在lastName属性,所以JS正确地将obj.lastName计算为undefined。

    在其他情况下,你知道变量期望保存一个对象或一个函数来返回一个对象。但是由于某些原因,你不能实例化该对象。在这种情况下,null是丢失对象的有意义的指示器。

    例如,clone()是一个克隆普通JS对象的函数,函数将返回一个对象

    function clone(obj) {

    if (typeof obj === 'object' && obj !== null) {

    return Object.assign({}, obj);

    }

    return null;

    }

    clone({name: 'John'}); // => {name: 'John'}

    clone(15); // => null

    clone(null); // => null

    但是,可以使用非对象参数调用clone(): 15或null(或者通常是一个原始值,null或undefined)。在这种情况下,函数不能创建克隆,因此返回null—— 一个缺失对象的指示符。

    typeof操作符区分了这两个值

    typeof undefined; // => 'undefined'

    typeof null; // => 'object'

    严格相等运算符===可以正确区分undefined和null:

    let nothing = undefined;

    let missingObject = null;

    nothing === missingObject; // => false

    总结

    undefined的存在是JS的允许性质的结果,它允许使用:

    未初始化的变量

    不存在的对象属性或方法

    访问越界索引的数组元素

    不返回任何结果的函数的调用结果

    大多数情况下直接与undefined进行比较是一种不好的做法。一个有效的策略是减少代码中undefined关键字的出现:

    减少未初始化变量的使用

    使变量生命周期变短并接近其使用的位置

    尽可能为变量分配初始值

    多敷衍 const 和 let

    使用默认值来表示无关紧要的函数参数

    验证属性是否存在或使用默认属性填充不安全对象

    避免使用稀疏数组

    关于Fundebug

    Fundebug专注于JavaScript、微信小程序、微信小游戏、支付宝小程序、React Native、Node.js和Java线上应用实时BUG监控。 自从2016年双十一正式上线,Fundebug累计处理了10亿+错误事件,付费客户有阳光保险、核桃编程、荔枝FM、掌门1对1、微脉、青团社等众多品牌企业。欢迎大家免费试用!

    展开全文
  • 目录 前言 一、基本概念 1、undefined 2、null 二、简单区别 ...undefined 和 null 的区别是个老生常谈的话题了,之前我对二者的区别只是简单理解,例如二者转成 Boolean 类型都是 false、使

    目录

    前言

    一、基本概念

    1、undefined

    2、null

    二、简单区别

    三、表现形式

    1、typeof

    2、== 与 ===

    3、Object.prototype.toString.call

    4、+ 运算 与 Number()

    5、JSON.stringify

    6、let undefiend = 'test'

    四、建议


    前言

    undefined 和 null 的区别是个老生常谈的话题了,之前我对二者的区别只是简单理解,例如二者转成 Boolean 类型都是 false、使用 == 进行比较时为 true、使用 === 进行比较时为 false 等,却没有真正系统地总结二者的区别。

    某天,下班前几分钟,我彻底弄懂了 undefined 和 null 的区别。

    一、基本概念

    1、undefined

    undefined 是“全局对象”的一个属性。也就是说,它是全局作用域的一个变量(下面展开对 undefined 变量的赋值操作)。undefined 的最初值就是原始数据类型 undefined。

    2、null

    null 是一个字面量,不像 undefined,它不是“全局对象”的一个属性。null 是表示缺少的标识,指示变量未指向任何对象。把 null 作为尚未创建的对象,或许更好理解。在 API 中,null 常使用来表示返回类型应是一个对象,但没有关联某个具体对象的这么一个值。

    二、简单区别

    总的来说,null 和 undefined 都表示空,主要区别在于 undefined 表示尚未初始化的变量的值,而 null 表示该变量有意缺少对象指向。

    • undefined

      • 这个变量从根本上就没有定义。

      • 隐藏式 空值。

    • null

      • 这个值虽然定义了,但它并未指向任何内存中的对象。

      • 声明式 空值。

    以下是一张经典的图片,帮助我们理解。

    三、表现形式

    undefined 和 null 在 JavaScript 中有什么不同的表现形式,理解这些表现形式,可以帮助我们更好地理解 undefined 和 null 的区别。

    1、typeof

    console.log(typeof undefined);   // 'undefined'
    console.log(typeof null);   // 'object'

    typeof null 为 object 是一个历史遗留问题,直到现阶段都无法被修复。

    在 JavaScript 初始版本中,值以 32位 存储。前 3位 表示数据类型的标记,其余位则表示值。
    对于所有对象类型,它的前 3位 都以 000 作为类型标记位。在 JavaScript 早期版本中,null 被认为是一个特殊的值,用来对应 C 中的空指针,但 JavaScript 中没有 C 中的指针概念,所以 null 意味着什么都没有或者 void 并以 全0(32位)表示。

    因此每当 JavaScript 读取 null 时,它的前 3位 将它视为对象类型,这也是为什么 typeof null 返回 object 的原因。

    2、== 与 ===

    console.log(null == undefined);   // true
    console.log(null === undefined);   // false
    console.log(!!null === !!undefined);   // true

    这一点相信大家都明白,== 比较的是值,而 === 比较的是值跟类型。undefined 和 null 的布尔值都为 false,因此在用 == 比较时,为 true;而 undefined 和 null 的类型不同,因此在用 === 比较时为 false。

    3、Object.prototype.toString.call

    console.log( Object.prototype.toString.call(undefined) );   // '[object Undefined]'
    console.log( Object.prototype.toString.call(null) );   // '[object Null]'

    toString() 是 Object 的原型方法,调用该方法,默认返回当前对象的 [[Class]]。这是一个内部属性,其格式为 [object Xxx],其中 Xxx 就是对象的类型。

    那么既然在 JavaScript 中,万物皆对象,为什么 xxx.toString() 不能返回变量类型?

    这是因为各个类中重写了 toString(),因此需要调用 Object 中的 toString(),且必须使用 toString.call() 的方式调用。对于 Object 对象,直接调用 toString() 就能返回 [object Object];而对于其他对象,则需要通过 call / apply 来调用才能返回正确的类型信息。

    4、+ 运算 与 Number()

    let a = undefined + 1;
    let b = null + 1;
    console.log(a);   // NaN
    console.log(b);   // 1
    
    console.log(Number(undefined));   // NaN
    console.log(Number(null));   // 0

    这涉及到 JavaScript 中的隐式类型转换,在执行加法运算前,隐式类型转换会尝试将表达式中的变量转换为 number 类型。如:'1' + 1 会得到结果 11。

    • null 转化为 number 时,会转换成 0。

    • undefined 转换为 number 时,会转换为 NaN。

    5、JSON.stringify

    console.log( JSON.stringify({a: undefined}) );   // '{}'
    console.log( JSON.stringify({b: null}) );   // '{b: null}'
    console.log( JSON.stringify({a: undefined, b: null}) );   // '{b: null}'

    JSON 会将 undefined 对应的 key 删除,这是因为 JSON 自身的转换原则。在 undefined 的情况下,有无该条数据是没有区别的,因为他们在表现形式上并无不同。

    let obj1 = { a: undefined };
    let obj2 = {};
    
    console.log(obj1.a);   // undefined
    console.log(obj2.a);   // undefined

    6、let undefiend = 'test'

    function test(params) {
        let undefined = 'test';   // 该作用域内undefined为一个变量,赋值为test
        return params === undefined;
    }
    
    test();   // false
    test(undefined);   // false
    test('test');   // ture
    
    let undefined = 'test';   // Uncaught SyntaxError: Identifier 'undefined' has already been declared

    JavaScript 对于 undefined 的限制方式为全局创建了一个只读的 undefined,但是并没有彻底禁止局部 undefined 变量的定义。

    请在任何时候,都不要对 undefined 变量进行覆盖,就算是你的 JSON 转换将 undefined 转换为 '' ,也不要通过该操作进行,这将是及其危险的行为。

    四、建议

    如果你需要使用 undefined 定义空值,请不要采取以下两种方式:

    • let a;

    • let a = undefined;

    进而采取下面这种方式显式声明 undefined:

    • let a = void 0;

    展开全文
  • 处理 JS中 undefined 的7个技巧

    千次阅读 2020-03-16 13:12:22
    今天跟大家分享下处理JS中undefined的7个技巧的知识。 前言 大约8年前,开始学习JS时,遇到了一个奇怪的情况,既存在undefined 的值,也存在表示空值的null。它们之间的明显区别是什么?它们似乎都定义了空值,而且,...

    今天跟大家分享下处理JS中undefined的7个技巧的知识。

    前言

    大约8年前,开始学习JS时,遇到了一个奇怪的情况,既存在undefined 的值,也存在表示空值的null。它们之间的明显区别是什么?它们似乎都定义了空值,而且,比较null == undefined的计算结果为true。

    大多数现代语言,如Ruby、Python或Java都有一个空值(nil或null),这似乎是一种合理的方式。

    对于JavaScript,解释器在访问尚未初始化的变量或对象属性时返回undefined。例如:

    let company;
    company;    // => undefined
    let person = { name: 'John Smith' };
    person.age; // => undefined
    

    另一方面,null表示缺少的对象引用,JS本身不会将变量或对象属性设置为null。

    一些原生方法,比如String.prototype.match(),可以返回null来表示丢失的对象。看看下面的示例:

    let array = null;  
    array;                // => null
    let movie = { name: 'Starship Troopers',  musicBy: null };
    movie.musicBy;        // => null
    'abc'.match(/[0-9]/); // => null
    

    由于 JS 的宽容特性,开发人员很容易访问未初始化的值,我也犯了这样的错误。

    通常,这种危险的操作会生成undefined 的相关错误,从而快速地结束脚本。相关的常见错误消息有:

    TypeError: 'undefined' is not a function
    TypeError: Cannot read property '<prop-name>' of undefined
    type errors
    

    JS 开发人员可以理解这个笑话的讽刺:

    functionundefined(){
    // problem solved
    }
    

    为了降低此类错误的风险,必须理解生成undefined的情况。更重要的是抑制它的出现并阻止在应用程序中传播,从而提高代码的持久性。

    让咱们详细讨论undefined 及其对代码安全性的影响。

    1 undefined是什么鬼

    JS 有6种基本类型

    Boolean: true 或 false
    Number: 1, 6.7, 0xFF
    String: “Gorilla and banana”
    Symbol: Symbol(“name”) (starting ES2015)
    Null: null
    Undefined: undefined.

    和一个单独的Object 类型:{name: “Dmitri”}, [“apple”, “orange”]。

    根据ECMAScript规范,从6种原始类型中,undefined是一个特殊的值,它有自己的Undefined类型。

    未为变量赋值时默认值为undefined。

    该标准明确定义,当访问未初始化的变量、不存在的对象属性、不存在的数组元素等时,将接收到一个undefined 的值。例如

    let number;
    number;     // => undefined
    let movie = { name: 'Interstellar' };
    movie.year; // => undefined
    let movies = ['Interstellar', 'Alexander'];
    movies[3];  // => undefined
    

    上述代码大致流程:

    未初始化的变量number

    一个不存在的对象属性movie.year

    或者不存在数组元素movies[3]

    都会被定义为undefined。

    ECMAScript规范定义了undefined 值的类型

    Undefined type是其唯一值为undefined 值的类型。

    在这个意义上,typeof undefined返回“undefined”字符串

    typeofundefined==='undefined';// => true
    

    当然typeof可以很好地验证变量是否包含undefined的值

    let nothing;
    typeofnothing ==='undefined';// => true
    

    2 创建未定义的常见场景

    2.1 未初始化变量
    尚未赋值(未初始)的声明变量默认为undefined。

    let myVariable;
    myVariable;// => undefined
    

    myVariable已声明,但尚未赋值,默认值为undefined。

    解决未初始化变量问题的有效方法是尽可能分配初始值。变量在未初始化状态中越少越好。理想情况下,你可以在声明const myVariable ='Initial value’之后立即指定一个值,但这并不总是可行的。

    技巧1:使用 let 和 const 来代替 var
    在我看来,ES6 最好的特性之一是使用const和let声明变量的新方法。const和let具有块作用域(与旧的函数作用域var相反),在声明行之前都存在于暂时性死区。

    当变量一次性且永久地接收到一个值时,建议使用const声明,它创建一个不可变的绑定。

    const的一个很好的特性是必须为变量const myVariable ='initial’分配一个初始值。变量未暴露给未初始化状态,并且访问undefined是不可能的。

    以下示例检查验证一个单词是否是回文的函数:

    function isPalindrome(word) {
      const length = word.length;
      const half = Math.floor(length / 2);
      for (let index = 0; index < half; index++) {
        if (word[index] !== word[length - index - 1]) {
          return false;
        }
      }
      return true;
    }
    isPalindrome('madam'); // => true
    isPalindrome('hello'); // => false
    

    length 和 half 变量被赋值一次。将它们声明为const似乎是合理的,因为这些变量不会改变。

    如果需要重新绑定变量(即多次赋值),请应用let声明。只要可能,立即为它赋一个初值,例如,let index = 0。

    那么使用 var 声明呢,相对于ES6,建议是完全停止使用它。

    var 声明的变量提会被提升到整个函数作用域顶部。可以在函数作用域末尾的某个地方声明var变量,但是仍然可以在声明之前访问它:对应变量的值是 undefined。

    相反,用let 或者 const 声明的变量之前不能访问该变量。之所以会发生这种情况,是因为变量在声明之前处于暂时死区。这很好,因为这样就很少有机会访问到 undefined 值。

    使用let(而不是var)更新的上述示例会引发ReferenceError 错误,因为无法访问暂时死区中的变量。

    function bigFunction() {
      // code...
      myVariable; // => Throws 'ReferenceError: myVariable is not defined'
      // code...
      let myVariable = 'Initial value';
      // code...
      myVariable; // => 'Initial value'
    }
    bigFunction();
    

    技巧2:增加内聚性
    内聚描述模块的元素(命名空间、类、方法、代码块)内聚在一起的程度。凝聚力的测量通常被称为高凝聚力或低内聚。
    高内聚是优选的,因为它建议设计模块的元素以仅关注单个任务,它构成了一个模块。
    专注且易懂:更容易理解模块的功能
    可维护且更容易重构:模块中的更改会影响更少的模块
    可重用:专注于单个任务,使模块更易于重用
    可测试:可以更轻松地测试专注于单个任务的模块
    在这里插入图片描述
    高内聚和低耦合是一个设计良好的系统的特征。

    代码块本身可能被视为一个小模块,为了尽可能实现高内聚,需要使变量尽可能接近使用它们代码块位置。

    例如,如果一个变量仅存在以形成块作用域内,不要将此变量公开给外部块作用域,因为外部块不应该关心此变量。

    不必要地延长变量生命周期的一个典型例子是函数中for循环的使用:

    function someFunc(array) {
      var index, item, length = array.length;
      // some code...
      // some code...
      for (index = 0; index < length; index++) {
        item = array[index];
        // some code...
      }
      return 'some result';
    }
    

    index,item和length变量在函数体的开头声明,但是,它们仅在最后使用,那么这种方式有什么问题呢?

    从顶部的声明到for语句中变量 index 和 item 都是未初始化的,值为 undefined。它们在整个函数作用域内具有不合理较长的生命周期。

    一种更好的方法是将这些变量尽可能地移动到使用它们的位置:

    function someFunc(array) {
      // some code...
      // some code...
      const length = array.length;
      for (let index = 0; index < length; index++) {
        const item = array[index];
        // some
      }
      return 'some result';
    }
    

    index和item变量仅存在于for语句的作用域内,for 之外没有任何意义。length变量也被声明为接近其使用它的位置。

    为什么修改后的版本优于初始版本?主要有几点:
    变量未暴露undefined状态,因此没有访问undefined的风险
    将变量尽可能地移动到它们的使用位置会增加代码的可读性
    高内聚的代码块在必要时更容易重构并提取到单独的函数中

    2.2 访问不存在的属性
    访问不存在的对象属性时,JS 返回undefined。

    咱们用一个例子来说明这一点:

    let favoriteMovie = {
      title: 'Blade Runner'
    };
    favoriteMovie.actors; // => undefined
    

    favoriteMovie是一个具有单个属性 title 的对象。使用属性访问器favoriteMovie.actors访问不存在的属性actors将被计算为undefined。

    本身访问不存在的属性不会引发错误, 但尝试从不存在的属性值中获取数据时就会出现问题。常见的的错误是 TypeError: Cannot read property of undefined。

    稍微修改前面的代码片段来说明TypeError throw:

    let favoriteMovie = {
      title: 'Blade Runner'
    };
    favoriteMovie.actors[0];
    // TypeError: Cannot read property '0' of undefined
    

    favoriteMovie没有属性actors,所以favoriteMovie.actors的值 undefined。因此,使用表达式favoriteMovie.actors[0]访问undefined值的第一项会引发TypeError。

    JS 允许访问不存在的属性,这种允许访问的特性容易引起混淆:可能设置了属性,也可能没有设置属性,绕过这个问题的理想方法是限制对象始终定义它所持有的属性。

    不幸的是,咱们常常无法控制对象。在不同的场景中,这些对象可能具有不同的属性集,因此,必须手动处理所有这些场景:

    接着我们实现一个函数append(array, toAppend),它的主要功能在数组的开头和/或末尾添加新的元素。toAppend参数接受具有属性的对象:
    first:元素插入数组的开头
    last:元素在数组末尾插入。

    函数返回一个新的数组实例,而不改变原始数组(即它是一个纯函数)。

    append()的第一个版本看起来比较简单,如下所示:

    function append(array, toAppend) {
      const arrayCopy = array.slice();
      if (toAppend.first) {
        arrayCopy.unshift(toAppend.first);
      }
      if (toAppend.last) {
        arrayCopy.push(toAppend.last);
      }
      return arrayCopy;
    }
    append([2, 3, 4], { first: 1, last: 5 }); // => [1, 2, 3, 4, 5]
    append(['Hello'], { last: 'World' });     // => ['Hello', 'World']
    append([8, 16], { first: 4 });            // => [4, 8, 16]
    

    由于toAppend对象可以省略first或last属性,因此必须验证toAppend中是否存在这些属性。如果属性不存在,则属性访问器值为undefined。

    检查first或last属性是否是undefined,在条件为 if(toappendix .first){}和if(toappendix .last){}中进行验证:

    这种方法有一个缺点, undefined,false,null,0,NaN和’'是虚值。

    在append() 的当前实现中,该函数不允许插入虚值元素:

    append([10], { first: 0, last: false }); // => [10]
    

    0和false是虚值的。因为 if(toAppend.first){}和if(toAppend.last){}实际上与falsy进行比较,所以这些元素不会插入到数组中,该函数返回初始数组[10]而不会进行任何修改。

    以下技巧解释了如何正确检查属性的存在。

    技巧3:检查属性是否存在
    JS 提供了许多方法来确定对象是否具有特定属性:

    obj.prop!== undefined:直接与undefined进行比较
    typeof obj.prop!=='undefined':验证属性值类型
    obj.hasOwnProperty('prop'):验证对象是否具有自己的属性
    'prop' in obj:验证对象是否具有自己的属性或继承属性
    

    我的建议是使用 in 操作符,它的语法短小精悍。in操作符的存在表明一个明确的意图,即检查对象是否具有特定的属性,而不访问实际的属性值。

    obj.hasOwnProperty(‘prop’)也是一个很好的解决方案,它比 in 操作符稍长,仅在对象自己的属性中进行验证。

    涉及与undefined进行比较剩下的两种方式可能有效,但在我看来,obj.prop!== undefined和typeof obj.prop!=='undefined’看起来冗长而怪异,并暴露出直接处理undefined的可疑路径。

    让咱们使用in操作符改进append(array, toAppend) 函数:

    function append(array, toAppend) {
      const arrayCopy = array.slice();
      if ('first' in toAppend) {
        arrayCopy.unshift(toAppend.first);
      }
      if ('last' in toAppend) {
        arrayCopy.push(toAppend.last);
      }
      return arrayCopy;
    }
    append([2, 3, 4], { first: 1, last: 5 }); // => [1, 2, 3, 4, 5]
    append([10], { first: 0, last: false });  // => [0, 10, false]
    

    ‘first’ in toAppend (和’last’ in toAppend)在对应属性存在时为true,否则为false。in操作符的使用解决了插入虚值元素0和false的问题。现在,在[10]的开头和结尾添加这些元素将产生预期的结果[0,10,false]。

    技巧4:解构访问对象属性
    在访问对象属性时,如果属性不存在,有时需要指示默认值。可以使用in和三元运算符来实现这一点。

    const object = { };
    const prop = 'prop' in object ? object.prop : 'default';
    prop; // => 'default'
    

    当要检查的属性数量增加时,三元运算符语法的使用变得令人生畏。对于每个属性,都必须创建新的代码行来处理默认值,这就增加了一堵难看的墙,里面都是外观相似的三元运算符。

    为了使用更优雅的方法,可以使用 ES6 对象的解构。

    对象解构允许将对象属性值直接提取到变量中,并在属性不存在时设置默认值,避免直接处理undefined的方便语法。

    实际上,属性提取现在看起来简短而有意义:

    const object = {  };
    const { prop = 'default' } = object;
    prop; // => 'default'
    

    要查看实际操作中的内容,让我们定义一个将字符串包装在引号中的有用函数。quote(subject, config)接受第一个参数作为要包装的字符串。第二个参数config是一个具有以下属性的对象:

    char:包装的字符,例如 '(单引号)或“(双引号),默认为”。

    skipIfQuoted:如果字符串已被引用则跳过引用的布尔值,默认为true。

    使用对象析构的优点,让咱们实现quote()

    function quote(str, config) {
      const { char = '"', skipIfQuoted = true } = config;
      const length = str.length;
      if (skipIfQuoted
          && str[0] === char
          && str[length - 1] === char) {
        return str;
      }
      return char + str + char;
    }
    quote('Hello World', { char: '*' });        // => '*Hello World*'
    quote('"Welcome"', { skipIfQuoted: true }); // => '"Welcome"'
    

    const {char = ‘", skipifquote = true} = config解构赋值在一行中从config对象中提取char和skipifquote属性。如果config对象中有一些属性不可用,那么解构赋值将设置默认值:char为’"’,skipifquote为false。

    该功能仍有改进的空间。让我们将解构赋值直接移动到参数部分。并为config参数设置一个默认值(空对象{}),以便在默认设置足够时跳过第二个参数。

    function quote(str, { char = '"', skipIfQuoted = true } = {}) {
      const length = str.length;
      if (skipIfQuoted
          && str[0] === char
          && str[length - 1] === char) {
        return str;
      }
      return char + str + char;
    }
    quote('Hello World', { char: '*' }); // => '*Hello World*'
    quote('Sunny day');                  // => '"Sunny day"'
    

    注意,解构赋值替换了函数 config 参数。我喜欢这样:quote()缩短了一行。={}在解构赋值的右侧,确保在完全没有指定第二个参数的情况下使用空对象。

    对象解构是一个强大的功能,可以有效地处理从对象中提取属性。我喜欢在被访问属性不存在时指定要返回的默认值的可能性。因为这样可以避免undefined以及与处理它相关的问题。

    技巧5:用默认属性填充对象
    如果不需要像解构赋值那样为每个属性创建变量,那么丢失某些属性的对象可以用默认值填充。

    ES6 Object.assign(target,source1,source2,…)将所有可枚举的自有属性的值从一个或多个源对象复制到目标对象中,该函数返回目标对象。

    例如,需要访问unsafeOptions对象的属性,该对象并不总是包含其完整的属性集。

    为了避免从unsafeOptions访问不存在的属性,让我们做一些调整:

    定义包含默认属性值的defaults对象

    调用Object.assign({},defaults,unsafeOptions)来构建新的对象options。新对象从unsafeOptions接收所有属性,但缺少的属性从defaults对象获取。

    const unsafeOptions = {
      fontSize: 18
    };
    const defaults = {
      fontSize: 16,
      color: 'black'
    };
    const options = Object.assign({}, defaults, unsafeOptions);
    options.fontSize; // => 18
    options.color;    // => 'black'
    

    unsafeOptions仅包含fontSize属性。defaults对象定义属性fontSize和color的默认值。

    Object.assign() 将第一个参数作为目标对象{}。目标对象从unsafeOptions源对象接收fontSize属性的值。并且人defaults对象的获取color属性值,因为unsafeOptions不包含color属性。

    枚举源对象的顺序很重要:后面的源对象属性会覆盖前面的源对象属性。

    现在可以安全地访问options对象的任何属性,包括options.color在最初的unsafeOptions中是不可用的。

    还有一种简单的方法就是使用ES6中展开运算符:

    const unsafeOptions = {
      fontSize: 18
    };
    const defaults = {
      fontSize: 16,
      color: 'black'
    };
    const options = {
      ...defaults,
      ...unsafeOptions
    };
    options.fontSize; // => 18
    options.color;    // => 'black'
    

    对象初始值设定项从defaults和unsafeOptions源对象扩展属性。指定源对象的顺序很重要,后面的源对象属性会覆盖前面的源对象。

    使用默认属性值填充不完整的对象是使代码安全且持久的有效策略。无论哪种情况,对象总是包含完整的属性集:并且无法生成undefined的属性。

    2.3 函数参数
    函数参数隐式默认为undefined。

    通常,用特定数量的参数定义的函数应该用相同数量的参数调用。在这种情况下,参数得到期望的值。

    function multiply(a, b) {
      a; // => 5
      b; // => 3
      return a * b;
    }
    multiply(5, 3); // => 15
    

    调用multiply(5,3)使参数a和b接收相应的5和3值,返回结果:5 * 3 = 15。

    在调用时省略参数会发生什么?

    function multiply(a, b) {
      a; // => 5
      b; // => undefined
      return a * b;
    }
    multiply(5); // => NaN
    

    函数multiply(a, b){}由两个参数a和b定义。调用multiply(5)用一个参数执行:结果一个参数是5,但是b参数是undefined。

    技巧6:使用默认参数值
    有时函数不需要调用的完整参数集,可以简单地为没有值的参数设置默认值。

    回顾前面的例子,让我们做一个改进,如果b参数未定义,则为其分配默认值2:

    function multiply(a, b) {
      if (b === undefined) {
        b = 2;
      }
      a; // => 5
      b; // => 2
      return a * b;
    }
    multiply(5); // => 10
    

    虽然所提供的分配默认值的方法有效,但不建议直接与undefined值进行比较。它很冗长,看起来像一个hack .

    这里可以使用 ES6 的默认值:

    function multiply(a, b = 2) {
      a; // => 5
      b; // => 2
      return a * b;
    }
    multiply(5);            // => 10
    multiply(5, undefined); // => 10
    

    2.4 函数返回值
    隐式地,没有return语句,JS 函数返回undefined。

    在JS中,没有任何return语句的函数隐式返回undefined:

    function square(x) {
      const res = x * x;
    }
    square(2); // => undefined
    

    square() 函数没有返回计算结果,函数调用时的结果undefined。

    当return语句后面没有表达式时,默认返回 undefined。

    function square(x) {
      const res = x * x;
      return;
    }
    square(2); // => undefined
    

    return; 语句被执行,但它不返回任何表达式,调用结果也是undefined。

    function square(x) {
      const res = x * x;
      return res;
    }
    square(2); // => 4
    

    技巧7:不要相信自动插入分号
    JS 中的以下语句列表必须以分号(;)结尾:

    空语句
    let,const,var,import,export声明
    表达语句
    debugger 语句
    continue 语句,break 语句
    throw 语句
    return 语句
    

    如果使用上述声明之一,请尽量务必在结尾处指明分号:

    function getNum() {
      let num = 1; 
      return num;
    }
    getNum(); // => 1
    

    let 声明和return 语句结束时,强制性写分号。

    当你不想写这些分号时会发生什么?例如,咱们想要减小源文件的大小。

    在这种情况下,ECMAScript 提供自动分号插入(ASI)机制,为你插入缺少的分号。

    ASI 的帮助下,可以从上一个示例中删除分号:

    function getNum() {
      // Notice that semicolons are missing
      let num = 1
      return num
    }
    getNum() // => 1
    

    上面的代码是有效的JS代码,缺少的分号ASI会自动为我们插入。

    乍一看,它看起来很 nice。ASI 机制允许你少写不必要的分号,可以使JS代码更小,更易于阅读。

    ASI 创建了一个小而烦人的陷阱。当换行符位于return和return \n expression之间时,ASI 会在换行符之前自动插入分号(return; \n expression)。

    函数内部return; ?即该函数返回undefined。如果你不详细了解ASI的机制,则意外返回的undefined会产生意想不到的问题。

    来 getPrimeNumbers()调用返回的值:

    function getPrimeNumbers() {
      return 
        [ 2, 3, 5, 7, 11, 13, 17 ]
    }
    getPrimeNumbers() // => undefined
    

    在return语句和数组之间存在一个换行,JS 在return后自动插入分号,解释代码如下:

    function getPrimeNumbers() {
      return; 
      [ 2, 3, 5, 7, 11, 13, 17 ];
    }
    getPrimeNumbers(); // => undefined
    

    return; 使函数getPrimeNumbers() 返回undefined而不是期望的数组。

    这个问题通过删除return和数组文字之间的换行来解决:

    function getPrimeNumbers() {
      return [ 
        2, 3, 5, 7, 11, 13, 17 
      ];
    }
    getPrimeNumbers(); // => [2, 3, 5, 7, 11, 13, 17]
    

    我的建议是研究自动分号插入的确切方式,以避免这种情况。

    当然,永远不要在return和返回的表达式之间放置换行符。

    2.5 void 操作符
    void 计算表达式无论计算结果如何都返回undefined

    void 1;                    // => undefined
    void (false);              // => undefined
    void {name: 'John Smith'}; // => undefined
    void Math.min(1, 3);       // => undefined
    

    void操作符的一个用例是将表达式求值限制为undefined,这依赖于求值的一些副作用。

    未定义的数组
    访问越界索引的数组元素时,会得到undefined 。

    const colors = ['blue','white','red'];
    colors[5];// => undefinedcolors[-1];
     // => undefined
    

    colors数组有3个元素,因此有效索引为0,1和2。

    因为索引5和-1没有数组元素,所以访问colors[5]和colors[-1]值为undefined。

    JS 中,可能会遇到所谓的稀疏数组。这些数组是有间隙的数组,也就是说,在某些索引中,没有定义元素。

    当在稀疏数组中访问间隙(也称为空槽)时,也会得到一个undefined。

    下面的示例生成稀疏数组并尝试访问它们的空槽

    const sparse1 = new Array(3);
    sparse1;       // => [<empty slot>, <empty slot>, <empty slot>]
    sparse1[0];    // => undefined
    sparse1[1];    // => undefined
    const sparse2 = ['white',  ,'blue']
    sparse2;       // => ['white', <empty slot>, 'blue']
    sparse2[1];    // => undefined
    

    使用数组时,为了避免获取undefined,请确保使用有效的数组索引并避免创建稀疏数组。

    undefined和null之间的区别
    一个合理的问题出现了:undefined和null之间的主要区别是什么?这两个特殊值都表示为空状态。

    主要区别在于undefined表示尚未初始化的变量的值,null表示故意不存在对象。

    让咱们通过一些例子来探讨它们之间的区别。

    number 定义了但没有赋值。

    let number;
    number;// => undefined 
    

    number 变量未定义,这清楚地表明未初始化的变量。

    当访问不存在的对象属性时,也会发生相同的未初始化概念

    const obj = {firstName:'Dmitri'};
    obj.lastName;// => undefined
    

    因为obj中不存在lastName属性,所以JS正确地将obj.lastName计算为undefined。

    在其他情况下,你知道变量期望保存一个对象或一个函数来返回一个对象。但是由于某些原因,你不能实例化该对象。在这种情况下,null是丢失对象的有意义的指示器。

    例如,clone()是一个克隆普通JS对象的函数,函数将返回一个对象

    function clone(obj) {
      if (typeof obj === 'object' && obj !== null) {
        return Object.assign({}, obj);
      }
      return null;
    }
    clone({name: 'John'}); // => {name: 'John'}
    clone(15);             // => null
    clone(null);           // => null
    

    但是,可以使用非对象参数调用clone(): 15或null(或者通常是一个原始值,null或undefined)。在这种情况下,函数不能创建克隆,因此返回null—— 一个缺失对象的指示符。

    typeof操作符区分了这两个值

    typeofundefined;// => 'undefined'
    typeof null;// => 'object'
    

    严格相等运算符===可以正确区分undefined和null:

    let nothing =undefined;
    let missingObject =null;
    nothing === missingObject;// => false
    

    3 总结

    undefined的存在是JS的允许性质的结果,它允许使用:

    未初始化的变量
    不存在的对象属性或方法
    访问越界索引的数组元素
    不返回任何结果的函数的调用结果
    大多数情况下直接与undefined进行比较是一种不好的做法。一个有效的策略是减少代码中undefined关键字的出现:
    减少未初始化变量的使用
    使变量生命周期变短并接近其使用的位置
    尽可能为变量分配初始值
    多敷衍 constlet
    使用默认值来表示无关紧要的函数参数
    验证属性是否存在或使用默认属性填充不安全对象
    避免使用稀疏数组
    

    关于处理JS中undefined的7个技巧,你学会了多少?欢迎在留言区评论!

    展开全文
  • undefined错误提示是我们上网,游戏,泡百度贴吧,微博,个人空间等经常碰到的问题,今天以百度贴吧为例,来简单说下undefined什么意思,是指什么,出现时怎么解决?1-百度贴吧大家都不陌生,我们打开一个百度贴吧,...
  • undefined reference to `d2i_X509_bio@OPENSSL_1_1_0' /usr/f_ck_gfw/libs/lib//libcurl.so: undefined reference to `OpenSSL_version_num@OPENSSL_1_1_0' /usr/f_ck_gfw/libs/lib//libcurl.so: undefined ...
  • undefined symbol 问题解决记录

    千次阅读 2020-08-12 11:46:06
    ./network: symbol lookup error: /usr/lib64/netPrnctl.so: undefined symbol: cupsGetDests 将解决方法与过程记录,以便日后查阅。 查找与分析原因 在编译时未出现问题,没有报错,成功编译生成动态库。以下为...
  • 解决Vue3的undefined问题

    千次阅读 2022-04-17 11:17:21
    vue3的undefined问题
  • JavaScript中undefined 和 null 区别

    千次阅读 2022-02-28 16:43:06
    大家应该都知道两个之间的区别,但是真正区分说明二者不同作用确实有点说...在 JavaScript 中, undefined 是一个没有设置值的变量,同时也是所有没有赋值变量的默认值,并且是自动赋值。 typeof 一个没有值的变量会返回
  • js对undefined的处理

    千次阅读 2021-03-15 01:18:53
    JavaScript 中有两个特殊数据类型:undefined 和 null,先看看 undefined 的判断,欢迎各位同仁交流一番:第一次碰见undefined的时候,我用的是java那一套,我是这样处理的if (obj== undefined){/*逻辑*/}事实说明我...
  • /usr/lib/gcc/x86_64-linux-gnu/5/../../../x86_64-linux-gnu/libgsl.so: undefined reference to `cblas_ctrmv' /usr/lib/gcc/x86_64-linux-gnu/5/../../../x86_64-linux-gnu/libgsl.so: un...
  • Undefined symbols for architecture arm64:

    千次阅读 2022-03-05 14:49:55
    Undefined symbols for architecture arm64:
  • undefined是什么意思啊?

    千次阅读 2021-03-16 12:08:51
    undefined是一个特殊值,通常用于指示变量尚未赋值。对未定义值的引用返回特殊值。其实大多数计算机言语,只要一个表示"无"的值,比方,C言语的NULL,Java言语的null,Python言语的None,Ruby言语的nil。undefined英...
  • 【前端面试题】undefined和null对比

    千次阅读 2022-03-14 21:33:20
    前端面试题:undefined和null
  • 在使用动态库开发部署时,遇到最多的问题可能就是 undefined symbol 了,导致这个出现这个问题的原因有多种多样,快速找到原因,采用对应的方法解决是本文写作的目的。 可能的原因 依赖库未找到 这是最常见的原因,...
  • JS 中的 undefined 和 null 的区别

    千次阅读 2021-08-19 13:33:26
    undefined 和 null 是 Javascript 中两种特殊的原始数据类型(Primary Type),它们都只有一个值,分别对应 undefined 和 null ,这两种不同类型的值,即有着不同的语义和场景,但又表现出较为相似的行为。 一、JS 中...
  • 爬坑记录1:-undefined undefined reference to *** openssl 无法链接 /usr/lib/x86_64-linux-gnu/libcurl.so: undefined reference to `PKCS12_PBE_add@OPENSSL_1.0.0' /usr/lib/x86_64-linux-gnu/libc...
  • 判断变量是否为undefined 1:typeof temp === 'undefined'; 2:temp === undefined 通过 typeof 判断,不管num变量是否定义都可以判断 通过 === 判断时num必须先定义,否则会报错 判断变量是否为null Object....
  • Undefined类型

    千次阅读 2019-02-22 14:37:53
    Undefined类型 undefined类型只有一个值即特殊的undefined。在使用var声明变量但未对其加以初始化时,这个变量的值就是undefined,例如: var message; alert(message == undefined); //true 这个例子只声明了变量...
  • Undefined与Null的区别

    千次阅读 2020-08-05 13:08:31
    Undefined与Null的区别 一、基本数据类型 在介绍undefined与null之前,我们先来了解一下ECMAScript中的数据类型。在ECMAScript中有六种简单数据类型(也称为基本数据类型): Undefined、Null、Boolean、Number 和 ...
  • undefined和null的区别

    千次阅读 2021-08-19 13:47:01
    1、undefined不是关键字,而null是关键字; 2、undefined和null被转换为布尔值的时候,两者都为false; 3、undefined在和null进行==比较时两者相等,全等于比较时两者不等 4、使用Number()对undefined和null...
  • undefined symbol的解决记录

    千次阅读 2022-02-11 18:14:29
    SOURCE_DIR}/lib/libinference_engine.so ${PROJECT_SOURCE_DIR}/lib/libtbb.so.2 -Wl,--end-group 无效 二、 又看这里说可能是链接时找不到头文件里的声明 参考开始的博文,使用ldd -r显示所有的undefined symbol ...
  • 单纯这个报错内容来看,很典型的undefined reference to的报错,这个报错在调用第三方库的编程中,简直是家常便饭,代表的含义也很明确,就是找不到函数的定义。 用C++写代码的都知道,在C++中,代码是分为声明和...
  • js中如何判断undefined

    万次阅读 2021-07-12 11:46:34
    js中如何判断undefined与null判断是否undefined判断是否null 判断是否undefined var a= undefined; if (typeof(a) == "undefined") { alert("undefined"); } 判断undefined需要用到typeof()方法 typeof的返回值 ...
  • JavaScript数据类型 - Undefined类型

    千次阅读 2020-08-28 10:10:05
    Undefined 类型 Undefined类型只有一个值,即特殊的undefined。在使用var声明变量但未对其进行初始化时,这个变量的值就是undefined,例如: var message; console.log(message); //undefined var message = ...
  • null 和undefined的区别

    千次阅读 2021-02-10 23:04:58
    undefined是定义了没有赋值3、null和undefined的运用场景undefined1、变量被声明时,但没有复制时,就等于undefined2、调用函数时,应该提供的参数没有提供,该参数等于undefined3、对象没有赋值的属性,该属性的值...
  • Keil 出现报错:undefined symbol

    千次阅读 2022-04-07 09:38:59
    Keil 出现报错:undefined symbol: 1.c文件没有添加进路径 2工程文件没有添加
  • 如何解决 Cannot set property of Undefined

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 709,260
精华内容 283,704
关键字:

undefined