一名【合格】前端工程师的自检清单之JavaScript基础

Author Avatar
w4ctech 6月22日
  • 在其它设备中阅读本文章

最后更新于2019年06月22日; 如遇到问题,请留言及时通知站长

logo
  1. JavaScript 规定了几种语言类型

    • 在 JS 中定义了 7 种数据类型?

      1. 基本类型:String、Number、Boolean、Symbol、Undefined、Null
      2. 引用类型:Object
  2. JavaScript 的底层数据结构是什么

    • 先了解什么是数据结构,什么是数据类型;
    1. 数据结构:数据结构是计算机存储和组织数据的方式。数据结构是指相互之间存在一种或多种特定关系的数据元素的集合。
    2. 数据类型:数据类型是数据结构中定义的一种性质相同的值的集合以及定义在这个值集合上的一组操作的总称。

      简单的理解就是数据结构是数据元素之间的组合,数据类型是有一些相同性质的数据元素。

      而在 javasript 中常见的数据结构就是栈与队列。

    3. 栈:是限定仅在表头进行插入和删除操作的线性表,就好比:一个死胡同,前面是“此路不通”,只有一个入口,如果一队人进入,只能队尾变对首出去。是一种先进后出的数据结构。
    4. 队列:同样它也是一种受限的线性表,不同的是它只允许在表的前端进行删除操作,在表的后端进行插入,就好比单行道,只能直行,不能调头。是一种先进先出的数据结构。

      除了上述的线性数据结构,还有一种常见的非线性的数据结构就是树。

    5. 树:在数据元素之间明显的存在一种层级关系,我们可以看作是“树”。通过递归来实现一定规律的树结构,是数据结构比较复杂的算法。

      堆栈、队列、树以及单向链接列表和双向链接列表都是 JS 底层的数据结构,也是编程语言中常用的数据结构。

  3. symbol 类型在实际开发中的应用、可手动实现一个简单的 Symbol

    • Symbol 是 Es6 引入的原始数据类型,表示一个独一无二的值。
    • symbol 的应用场景:

      1. 使用 Symbol 来作为对象属性名 (key);
      2. 使用 Symbol 来替代常量;
      3. 使用 Symbol 定义类的私有属性 / 方法。
    • symbol 的一些特性:

      1. Symbol 值通过 Symbol 函数生成,使用 typeof,结果为 "symbol";
      2. Symbol 函数前不能使用 new 命令,否则会报错;
      3. instanceof 的结果为 false;
      4. 可以接受一个字符串作为参数,表示对 Symbol 实例的描述,参数为对象会自动转化为字符串;
      5. Symbol 值不能与其他类型的值进行运算,会报错。
      6. Symbol 值可以显式转为字符串。
      7. Symbol 值可以作为标识符,用于对象的属性名,可以保证不会出现同名的属性。
      8. Symbol 作为属性名,该属性不会出现在 for...in、for...of 循环中,也不会被 Object.keys()、
      9. Object.getOwnPropertyNames()、JSON.stringify() 返回。但是,它也不是私有属性,有一个
      10. Object.getOwnPropertySymbols 方法,可以获取指定对象的所有 Symbol 属性名。
    • 手动实现一个简单的 symbol,相对与 symbol 的众多特性来说,要想完全模拟是不可能的,我们只能模拟一些特性。通过 Object 的一些方法来简单的实现一个属性名唯一的对象:

      (function() {
              var root = this;
      
              var generateName = (function(){
                  var postfix = 0;
                  return function(descString){
                      postfix++;
                      return 'symbol' + descString + '_' + postfix
                  }
              })()
          
              var SymbolPolyfill = function Symbol(description) {
          
                  if (this instanceof SymbolPolyfill) throw new TypeError('Symbol is not a constructor');
          
                  var descString = description === undefined ? undefined : String(description)
          
                  var symbol = Object.create({
                      toString: function() {
                          return this.__Name__;
                      }
                  })
          
                  Object.defineProperties(symbol, {
                      '__Description__': {
                          value: descString,
                          writable: false,
                          enumerable: false,
                          configurable: false
                      },
                      '__Name__': {
                          value: generateName(descString),
                          writable: false,
                          enumerable: false,
                          configurable: false
                      }
                  });
                  return symbol;
              }
              root.SymbolPolyfill = SymbolPolyfill;
          })()
  4. JavaScript 中的变量在内存中的具体存储形式

    • 在 JS 中变量可以用来保存两种类型的值:基本类型和引用类型。

      1. 基本类型值在内存中占据固定大小的空间,因此被保存在栈内存中;
      2. 通过一个变量赋值到另一个变量的基本类型,只是创建了这个值的副本;
      3. 引用类型的值是对象,保存在堆内存中;
      4. 包含引用类型的值的变量,是一个指向该对象的指针;
      5. 通过一个变量赋值到另一个变量的引用类型,只是复制了指针,因此都指向该对象;
      6. 确定一个值是哪种基本类型可以使用 typeof 操作符;
      7. 确定一个值是哪种引用类型可以使用 instanceof 操作符;
      8. 无论是基本类型还是引用类型都存在一个执行环境当中,这个执行环境决定变量的生命周期;每一个新的执行环境都会有一个作用域链,用来搜索变量和函数。
      9. 这个执行环境分为全局作用域和函数作用域;
      10. 全局作用域只能访问全局作用域的定义的函数和变量,无法访问局部环境(函数作用域)中的函数和变量;而局部环境可以访问全局作用域的任何数据;

        优化内存最好的方式就是在变量所存储的数据无用的时候,将其值设为 null,原值脱离执行环境,将会在下次运行时,被垃圾回收机制处理掉。

  5. 基本类型对应的内置对象,以及他们之间的装箱拆箱操作

    • 把基本数据类型转换为对应的引用类型的操作称为装箱,把引用类型转换为基本的数据类型称为拆箱。
    • 每当读取一个基本类型的时候,后台就会创建一个对应的基本包装类型对象,从而让我们能够调用一些方法来操作这些数据。
    • 装箱有两种方式:显式和隐式。

      var num = 9;
      num.toFixed(2);// "9.00"
      1. 隐式装箱
      var num = new Number(9);
      num.toFixed(2);// "9.00"
      num = null;    
      // 每次调用都会执行如上流程,
      1. 显式装箱
      var num = new Number(9);
      // num  Number{9}  
      • 拆箱操作可以通过一些操作符来实现,但实际调用的是 toString() 和 valueOf()。
      var num = new Number(9);
      num+'';//'9'
    • 拆箱

      num.toString() 
      • 如果是 new String(), 调用 valueOf()
  6. 理解值类型和引用类型

    • 值类型(stack)也就是基本数据类型,放在栈中,因其大小固定;引用类型(heap)放在堆中,因其大小不固定。
      但是对基本类型的操作(string、number)会执行装箱的操作,会转化成引用类型。
      同样对引用类型的操作也可能会执行拆箱的操作,转换为值类型。
  7. null 和 undefined 的区别

    • undefined 表示声明了变量,但未初始化,它的值为 undefined。
    • null 表示的是一个空对象指针,可以用 typeof 运算得到“object”。
    • 他们的出现的场景也不同:

      1. null 是对象原型链的终点;
      2. 调用函数时,应该传入的参数没有传递,就是 undefined;
      3. 函数没有返回值,默认是 undefined;
      4. 对象的属性没有赋值,该属性值为 undefined;
      5. 变量声明未赋值,值为 undefined;
      6. undefined 更多的表示“缺少的”值,而 null 更多的表示“空”。
  8. 至少可以说出三种判断 JavaScript 数据类型的方式,以及他们的优缺点,如何准确的判断数组类型

    • 常用的类型检查有 4 种;

      1. typeof 操作符:typeof 是一个操作符,不是一个函数,所以后面的括号没有特定的意义,返回的结果包括:number、boolean、string、object、undefined、function 等 6 种数据类型;

        • 优点:对基本类型的判断较为准确;
        • 缺点:无法精准的判断引用类型,null、Array 判断为‘object’。
      2. instanceof 操作符:instanceof 是判断一个对象和函数在原型链上是否有关系;如果有,返回 true,否则为 false;

        • 优点:判断引用类型较为准确;
        • 缺点:无法对基本类型作出精准的判断;它限定了我们的数据类型必须是用 new 出来的,才能准确的判断。
      3. constructor:每个函数的定义,都会生成一个 constructor;基本类型会执行隐时的执行装箱操作,创造一个构造函数的实例。

        • 优点:对基本类型和引入类型都可以判断;
        • 缺点:无法判断 null 和 undefined,而且 constructor 是可以修改的,会导致检查结果的不准确。
      4. Object.prototype.toString.call():无论什么类型都可以判断,返回类型的格式为 "[ object Xxx]"。

        • 缺点:Object.prototype.toString 本身也可能被修改。
        • 对于数组的判断,可以通过 instanceof、Object.prototype.toString.call()来判断。
  9. 可能发生隐式类型转换的场景以及转换原则,应如何避免或巧妙应用

    • 在 JS 中发生隐式转换的场景:

      1. 判断语句中:if 语句、while 语句、switch 语句等,会隐式转化为布尔值,来决定是否执行下面的流程。

        • 操作符引起的:布尔操作符(!、||、&&)、相等操作符(==、!=、===)等等;
    上面所说的是隐式转化为布尔值的一些场景,还有隐式转化成String和Number的。

       - +运算符可以把Number类型的转化为String;
       - -、*、/等运算符可以把String类型的转化成Number;

    2. 转化原则:布尔值的转换遵循是非原则,由返回值true和false决定;String和Number的转化是隐式转化为Object,调用Object的一些方式来实现转换。
    3. 如何避免:使用类型检查来防止转换导致的结果超出预知。
    4. 巧妙应用:布尔值的转换经常用在条件语句中,便于我们判断是否执行以下流程,防止出错,而其他的类型转换会很大程度的出现未知错误,所以少用,明确变量类型之后再用。
        ```
        var a = {
            i:0,
            valueOf:function(){
                return ++this.i;
            }
        }

        if( a== 1 && a==2 && a==3){
            console.log(1);
        }
        //1
        ```
  1. 出现小数精度丢失的原因, JavaScript 可以存储的最大数字、最大安全数字, JavaScript 处理大数字的方法、避免精度丢失的方法

    • 出现精度丢失的原因:在 js 中,采用双精度存储,占用 64bit。1 位用来表示符号位,11 位用来表示指数,52 位用来表示尾数。

      1. 这也是导致两个浮点数相加会出现问题
      0.1+0.2
      //0.30000000000000004 
      0.1+0.22
      //0.32
    • 因为尾数最大为 52,所以在 js 中可以存储的最大安全数字 Math.pow(2,53)-1,也就是 Number.MAX_SAFE_INTEGER;
    • 可以存储的最大数字为 Number.MAX_VALUE;

      Number.MAX_SAFE_INTEGER
      //9007199254740991
      Number.MAX_VALUE
      //1.7976931348623157e+308
    • 对于超出安全数字的操作,js 提供了 BigInt。

      1. BigInt 是 JavaScript 中的一个新的原始类型,可以用任意精度表示整数。使用 BigInt,即使超出 JavaScript Number 的安全整数限制,也可以安全地存储和操作大整数。
      2. 要创建一个 BigInt,在数字后面添加 n 后缀即可。

        1234567890123456789n * 123nR1234567890123456789n * 123n
        //151851850485185185047n
      3. 但是 BigInt 和 Number 之间不能混合操作。
      4. 对于浮点数的处理,为了避免精度丢失,通常情况下,我们会转化为整数处理,然后通过算数运算符转化为浮点数。

本文链接:https://i.w4ctech.cn/frontWeb/javascript.html
This blog is under a CC BY-NC-SA 3.0 Unported License