Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

变量

1. 变量声明

var 和 let

  • var 是 ES5 的声明关键字

    • 会参与预解析并存在变量提升(变量名提前使用不会报错,只是输出 undefined 值)
    • 存在于函数作用域(只要不离开函数,都一直有效,只有一份且任何地方都能改都能取)或全局作用域
    • 可以重复声明
  • let 是 ES6 新增的声明关键字

    • 不存在 var 的那种变量提升(提前使用会报错,但也会有提升,只不过通过暂时性死区阻止了提前使用)
    • 加入了块级作用域(离开了 for 或者 if 语句,就失效了,循环过程中的每一个块里面都会有一份新的变量)
    • 不允许重复声明
  • 例子

    for (let i = 0; i < 5; i++) {}
    console.log(i); //输出i is undefined
    
    • 如果改成 var 的话,函数内一直有效,到外面也有效,就能输出 5,但这样子就产生了变量污染
    var arr = [];
    for (let i = 0; i < 5; i++) {
      //把函数存到数组中,等待循环结束后调用
      arr[i] = function () {
        console.log(i);
      };
    }
    arr[1](); //输出结果是1,因为有闭包,且每次i都是新生成的一份
    
    • 如果改成 var 的话,函数内一直有效,只有一份,任何地方都能改都能取,就全都输出循环结束后的 5

const

  • 定义一个只读的常量,后面不能再改

    const y;
    
    • 这样是错的,一旦声明了就必须要赋值
  • 如果定义的是对象

    const obj = {};
    obj.id = 123;
    
    • 不会报错!对象里面的属性还是能增删改的,只是指向的对象引用不能再改
  • 如果定义的是数组

    const arr = [];
    arr.push(1);
    arr[1] = 2;
    
    • 不会报错!与上面对象同理

2. 变量类型

6 大基本类型(primitive values)

  • 先讲前 4 种

    基本类型对应的类含义
    numberNumber数字
    stringString字符串
    booleanBoolean布尔值
    undefined未定义
    • 【创建方式 1】字面量,视为基本类型,必须用typeof判断

      let count1 = 0;
      console.log(typeof count1); //"number"
      
      let name1 = "Tom";
      console.log(typeof name1); //"string"
      
      let flag1 = false;
      console.log(typeof flag1); //"boolean"
      
      let a;
      console.log(typeof a); //"undefined"
      
    • 【创建方式 2】new 对应的类,视为类的实例,必须用instance of判断

      let count2 = new Number(0);
      console.log(name2 instanceof Number); //true
      
      let name2 = new String("Tom");
      console.log(name2 instanceof String); //true
      
      let flag2 = new Boolean(false);
      console.log(name2 instanceof Number); //true
      
    • 注意:用不同方式声明的变量,必须要用对应的方式判断才能得到 true 的结果

  • 再讲 1 种(较特殊)

    基本类型含义
    null
    • 为什么说它特殊呢?
      • 因为 typeof 的结果并不是 null,而是 object,但它本质上并不是一个对象,而是一种内置的基本类型
    • 【创建方式】字面量
      let id = null;
      console.log(typeof id); //"object"
      console.log(id instanceof Object); //false
      
    • 可以理解为,最初设计时typeof null就被定义为返回"object",因为它具有 0 的开头,跟 object 对应的类型定义刚好吻合
  • 再讲 1 种(ES6 新增)

    基本类型含义
    symbol符号
    • 【创建方式】调用 Symbol 函数
      let s = Symbol("abc");
      console.log(typeof s); //"symbol"
      
    • 每一个都是独一无二的,不可能相等

5 大引用类型(reference values)

  • 先讲前 2 种

    引用类型对应的类含义
    objectObject对象
    functionFunction函数
    • 【创建方式 1】字面量

      let obj1 = {};
      console.log(typeof obj1); //"object"
      console.log(obj1 instanceof Object); //true
      
      let func1 = (a, b) => {
        return a + b;
      };
      console.log(typeof func1); //"function"
      console.log(func1 instanceof Function); //true
      
    • 【创建方式 2】new 对应的类

      let obj2 = new Object();
      console.log(typeof obj2); //"object"
      console.log(obj2 instanceof Object); //true
      
      let func2 = new Function("a", "b", "return a + b;");
      console.log(typeof func2); //function
      console.log(func2 instanceof Function); //true
      
    • typeof判断和instance of判断均可用,二者等价

  • 再讲 3 种(较特殊)

    引用类型对应类含义
    Array数组
    Date日期
    RegExp正则
    • 为什么说特殊呢?

      • 因为 typeof 的结果均为 object,它们本质上就是一个对象
    • 【创建方式 1】字面量

      let arr1 = [1, 2, 3];
      console.log(typeof arr1); //"object"
      console.log(arr1 instanceof Array); //true
      
      let r1 = /[a-z]:\\s/i;
      console.log(typeof r1); //"object"
      console.log(r1 instanceof RegExp); //true
      
    • 【创建方式 2】new 对应的类

      let arr2 = new Array(1, 2, 3);
      console.log(typeof arr2); //"object"
      console.log(arr2 instanceof Array); //true
      
      let r2 = new RegExp("[a-z]\\s", "i");
      console.log(typeof r2); //"object"
      console.log(r2 instanceof RegExp); //true
      
      let d1 = new Date("December 25, 2020"); //仅支持new
      console.log(typeof d1); //"object"
      console.log(d1 instanceof Date); //true
      
    • typeof判断和instance of判断均可用,后者更具体

3. 类型判断

利用 Object 上的 toString 方法

  • 原理

    • 每一个继承自 Object 的对象,都会从原型链中继承到其中的 toString 方法
      • 如果子类对象没有重写 toString 方法,那就会直接调用继承的,返回[Object type],这里 type 就是类型
      • 但大多数对象,都重写了 toString 方法,直接返回的是代表值的字符串,所以必须显式调用 Object 上的方法(注意,这里并没有改变 this,只是改变了调用的方法)
  • 解释原理的例子

    const arr = [1, 2];
    arr.toString === Object.prototype.toString;
    // false, 所以两者不同,实际上数组上重写了 toString 方法
    
    const o = { o: 1 };
    o.toString === Object.prototype.toString;
    // true, 所以对象默认不需要如此调用。但如果将对象的方法改写就不一定了
    
    o.toString = function changedToString() {
      return "haha";
    };
    o.toString(); // 'haha'
    Object.prototype.toString.call(o);
    // '[object Object]'. 发现 Object.prototype.toString 也是可以被改写的...
    
  • 判断数组的例子

    const an = ["Hello", "An"];
    an.toString();
    // "Hello,An"
    
    Object.prototype.toString.call(an);
    // "[object Array]"
    
    • 经常会再链式调用一个.slice(8, -1), 直接拿到Array五个字母
  • 其他类型的例子

    Object.prototype.toString.call("An"); // "[object String]"
    Object.prototype.toString.call(1); // "[object Number]"
    Object.prototype.toString.call(Symbol(1)); // "[object Symbol]"
    Object.prototype.toString.call(null); // "[object Null]"
    Object.prototype.toString.call(undefined); // "[object Undefined]"
    Object.prototype.toString.call(function () {}); // "[object Function]"
    Object.prototype.toString.call({ name: "An" }); // "[object Object]"
    

利用 instanceof 判断对象具体类型

  • 用法

    对象 instanceof 类型;
    
  • 原理

    • 判断对象的原型链中是不是能找到类型的 prototype
  • 注意

    • 只能用来判断对象类型,原始类型不可以
    • 所有对象类型 instanceof Object 都是 true
  • 判断数组的例子

    [] instanceof Array; // true
    

利用 Array.isArray()判断数组

  • 原理

    • ES5 新增的一个方法,当不存在时,可以 polyfill 为 toString
      if (!Array.isArray) {
        Array.isArray = function (arg) {
          return Object.prototype.toString.call(arg) === "[object Array]";
        };
      }
      
  • 注意

    • 当检测 Array 实例时,Array.isArray 优于 instanceof ,因为 Array.isArray 可以检测出 iframes

      var iframe = document.createElement("iframe");
      document.body.appendChild(iframe);
      xArray = window.frames[window.frames.length - 1].Array;
      var arr = new xArray(1, 2, 3); // [1,2,3]
      
      // Correctly checking for Array
      Array.isArray(arr); // true
      Object.prototype.toString.call(arr); // true
      // Considered harmful, because doesn't work though iframes
      arr instanceof Array; // false
      

4. 函数形态

两种简单形态

  • 声明

    function func1() {
      console.log("声明");
    }
    
    • 特性: 具备提升的效果,可以在这几行之前就进行调用
    • 调用
      func1();
      
  • 表达式

    let func2 = function () {
      console.log("表达式");
    };
    
    let func2 = () => {
      console.log("表达式");
    };
    
    • 不会提升
    • 调用
      func2();
      
    • 表达式一般可以和箭头函数一起用,但会有弊端:
      • 丧失 arguments 对象
      • 不能作为构造函数
  • 以上两种都可以嵌套到新的函数里,构成嵌套形态

两种复杂形态

  • 嵌套形态

    • 自行组合
  • 闭包形态

    let func3 = () => {
      let a = "123";
      return () => {
        console.log("闭包");
      };
    };
    
    • 调用
      func3()();
      

IIFE 立即执行

  • 匿名调用

    (function () {})();
    
    • 定义 和 调用 放在一起,不用起名字
  • 有名调用——可以用递归

    (function func(i) {
        console.log(i)
        if i < 3 {
            func(i++)
        }
    })(1)
    
  • 【不建议】匿名调用其实也可以用递归

    (function (i) {
        console.log(i)
        if i < 3 {
            arguments.callee(i++)
        }
    })(1)
    
    • 在严格模式下,ES5 已经禁止了这种用法

5. 函数参数

形参

  • 明确一点: 形参永远都是按值传递

  • 对于基本类型,传递给形参的是原始值

    • 在里面改动是无效的

      • 错误示范

        let swap = (a, b) => {
          let temp = a;
          a = b;
          b = temp;
        };
        
        let a = 1,
          b = 2;
        swap(a, b); //交换
        console.log(a); //1 没有变化
        console.log(b); //2 没有变化
        
  • 对于引用类型,传递给形参的也是值,只不过这个值是一个内存地址

    • 直接改动这个值也是无效的

      • 错误示范
        let twice = (arr) => {
          arr = [2, 4];
        };
        let arr = [1, 2];
        twice(arr);
        console.log(arr); //[1, 2] 没有变化
        
    • 但间接改动是有效的,你可以对 arr 内部进行操作变换,相当于对内存地址指向的区域进行改动

      • 正确示范
        let twice = (arr) => {
          for (let i = 0; i < arr.length; i++) {
            arr[i] = arr[i] * 2;
          }
        };
        let arr = [1, 2];
        twice(arr);
        console.log(arr); //[2, 4] 成功