JavaScript基础回顾(一):数据类型、变量、作用域、函数、this

72 阅读7分钟

数据类型

JavaScript 是一种有着动态类型的动态语言。JavaScript 中的变量与任何特定值类型没有任何关联,任何变量都可以被赋值(和重新赋值)各种类型的值。

JavaScript 也是一种弱类型语言,这意味着当操作涉及不匹配的类型时,它允许隐式类型转换,而不是抛出类型错误。

JavaScript 包含以下数据类型:

  • 原始数据类型(Primitive Data Types)
    • Number:整数或浮点数,不能精确计算大于 2^53 - 1 的整数
    • String:由字符组成的文本数据
    • Boolean:逻辑值 true 或 false
    • undefined:变量被声明了但没有被赋值
    • null:空值
    • Symbol(ES2015 新增):唯一的、不可改变的数据类型,常用于对象属性的键
    • BigInt(ES2020 新增):可以精确地表示任意大的 的整数
  • 复合数据类型(Object Types)
    • Object:无序的键值对集合,可以包含任意类型的数据
    • Array:特殊的对象,有序的元素集合,元素可以是任意类型
    • Function:特殊的对象,表示可执行的代码块
    • Map(ES2015):有序的键值对集合
    • Set(ES2015):有序元素(每个元素都是唯一的)集合

另外还有以下内置对象(Built-in Objects):

  • Date:用于处理日期和时间
  • RegExp:用于表示正则表达式
  • Error:表示错误信息的对象
  • JSON:用于处理 JSON 数据的解析和字符串化
  • Math:提供数学计算的方法
  • Promise:用于异步计算的对象

判断变量的数据类型

获得变量数据类型的方法主要有这些:

  • typeof 操作符:返回一个字符串,表示未经计算的值的类型,适用于识别除了 null 原始数据类型。除了函数,所有复合类型都识别为 object,null 也识别为 object。
  • Object.prototype.toString.call() 方法:方法返回一个表示对象类型的字符串,能够区分常用的所有数据类型。

以下为查询表:

数据类型typeof 结果Object.prototype.toString.call() 结果type_of_var() 方法
Numbernumber[object Number]number
Stringstring[object String]string
Booleanboolean[object Boolean]boolean
undefinedundefined[object Undefined]undefined
nullobject[object Null]null
Symbolsymbol[object Symbol]symbol
BigIntbigint[object BigInt]bigint
Arrayobject[object Array]array
Objectobject[object Object]object
Setobject[object Set]set
Mapobject[object Map]map
Functionfunction[object Function]function
Generatorfunction[object GeneratorFunction]generator
Errorobject[object Error]error
Dateobject[object Date]date
Promiseobject[object Promise]promise

Generator 不是数据类型,而是一种特殊函数,这里是好奇打印一下看看

上面有个 type_of_var 方法,是基于 Object.prototype.toString.call() 方法做的封装,程序员嘛肯定是能省就省:

function type_of_var(p_var) {
  try {
    let type = Object.prototype.toString.call(p_var);
    const res_match = type.match(/[object (\w+)]/);
    type = res_match[1].toLowerCase();

    if (type === 'generatorfunction') {
      return 'generator'
    }
    return type;
  } catch (err) {
    console.warn(err);
    return 'catch';
  }
}

变量

在 JavaScript 中,变量的声明使用 var、let 和 const 关键字。var 是旧的声明方式,没有块级作用域;let 和 const 是 ES2015 进入的,它们有块级作用域。

作用域

作用域决定了变量和函数的可见性。JavaScript 有全局作用域、函数作用域和块级作用域。

  • 全局作用域:在全局作用域中生命的变量在整个脚本中都是可见的
  • 函数作用域:在函数内部声明的变量只在该函数内部可见
  • 块级作用域:在代码块(如 if 语句、for循环等)内部声明的变量只在该代码块内部可见

理解闭包

1. 函数作用域

JavaScript 函数有自己的作用域,这意味着在函数内部声明的变量在函数外部是不可见的。当函数执行完毕后,其内部的变量通常会被销毁,以便释放内存。但是,如果函数内部含有一个函数,并且这个函数引用了外部函数的变量,那么即使这个外部函数执行完毕,这些变量也不会被销毁。

2. 闭包的定义

闭包是指一个函数及其周围的状态(词法环境)的组合。换句话说,闭包让你可以访问一个函数内部的变量,及时该函数已经执行完毕。这是因为闭包实际上保存了函数的词法环境,包括函数声明时的作用域链。

3. 如何创建闭包

  • 当一个函数返回另外一个函数时
  • 当一个函数作为另外一个函数的回调函数时
  • 当使用立即执行函数表达式时

4. 闭包的用途

  • 延长变量的生命周期:闭包可以保持变量的状态,及时创建他们的函数已经执行完毕
  • 数据封装:通过闭包可以创建私有变量,这些变量只能在闭包中访问,从而实现数据的封装
  • 模块化:闭包可以用来创建模块,模块可以公开接口函数,同时隐藏内部实现细节

5. 示例

function createCounter() {
  let count = 0;
  return function() {
    count += 1;
    console.log(count);
  };
}

const counter = createCounter();
counter(); // 输出: 1
counter(); // 输出: 2
counter(); // 输出: 3

在这个例子中,createCounter 函数返回了一个闭包。这个闭包可以访问并修改 createCounter 函数内部的 count 变量。每次调用 counter 函数时,都会总结 count 的值,并打印出来。

6. 闭包与内存管理

由于闭包会保持对外部函数作用域的引用,这意味着相关的变量不会被函数回收机制回收,直到闭包不再被使用。这可能导致内存使用增加,特别实在大量使用闭包的情况下。因此,在使用闭包时,需要注意及时释放不再使用的闭包,以避免内存泄漏。

函数

函数是 JavaScript 中执行特定任务的代码块。我们可以通过定义函数来封装可重用的代码。在 ES2015 中,新增了箭头函数。

普通函数与箭头函数的区别

1. this 值

  • 普通函数有 this 值,它取决于函数式如何被调用的
  • 箭头函数不绑定自己的 this,它们捕获其所在上下文的 this 值

2. 原型

  • 普通函数有 prototype 属性,允许定义函数的原型
  • 箭头函数没有

3. 参数

  • 普通函数有 arguments 对象,它包含了函数调用时传入的所有参数
  • 箭头函数没有 arguments 对象,但是可以通过(...args)来访问所有参数

4. 使用场景

  • 普通函数通常用于定义可复用的功能块,特别是需要绑定特定 this 或需要作为构造函数使用时
  • 箭头函数通常用于编写更简洁的函数表达式,特别是回调函数中,因为他们不绑定自己的 this

this 关键字

this 关键字在 JavaScript 中用于引用函数的执行上下文。它的值取决于函数式如何被调用的。

  • 当函数作为对象的方法调用时,this 指向该对象
  • 当函数作为普通函数调用时,this 指向全局对象(在浏览器中是 window 对象)
  • 当使用 new 关键字调用构造函数时,this 指向新创建的对象

可以改变函数上下文的方法

1. call 方法

调用一个对象的某个方法,以该对象作为上下文(this)来调用该方法。

function greet() {
  console.log(this.name);
}
const obj = { name: 'Alice' };
greet.call(obj); // 输出: Alice

2. apply 方法

与 call 方法类似,但是接受一个参数数组

function sum(a, b) {
  console.log(this.name + ' says sum is: ' + (a + b));
}
const obj = { name: 'Alice' };
sum.apply(obj, [1, 2]); // 输出: Alice says sum is: 3

3. bind 方法

创建一个新的函数,在这个新函数的调用中,this 被指定为 bind 方法的第一个参数,而其余参数将作为新函数的参数。

function greet() {
  console.log('Hello, ' + this.name);
}
const obj = { name: 'Alice' };
const boundGreet = greet.bind(obj);
boundGreet(); // 输出: Hello, Alice

4. 箭头函数

箭头函数没有自己的 this,它会捕获其所在上下文的 this 作为自己的 this 值。

const obj = {
  name: 'Alice',
  greet: function() {
    const arrowFunc = () => {
      console.log('Hello, ' + this.name);
    };
    arrowFunc();
  }
};

obj.greet(); // 输出: Hello, Alice