Javascript基础-引用类型、块级作用域、var let const、词法作用域、作用域链

✊不积跬步,无以至千里;不积小流,无以成江海。

引用类型与案例分析

引用类型是一种用于存储和操作复杂数据的数据类型。与基本数据类型(如数字、字符串等)不同,引用类型是由多个值组成的对象,可以包含属性和方法。以下是几种常见的引用类型及其案例分析:

  1. Object 对象:
    Object 是 JavaScript 的基础引用类型,用于存储键值对(属性和值)的集合。对象可以通过字面量或构造函数创建。
ini 复制代码
// 使用字面量创建对象
var person = {
  name: "John",
  age: 30,
  city: "New York"
};

// 使用构造函数创建对象
var person = new Object();
person.name = "John";
person.age = 30;
person.city = "New York";

对象的属性可以通过点语法或方括号语法进行访问和修改。

  1. Array 数组: Array 是一种用于存储多个值的有序集合,可以通过索引访问和修改数组中的元素。
javascript 复制代码
var fruits = ["apple", "banana", "orange"]; // 使用字面量创建数组

var numbers = new Array(1, 2, 3, 4, 5); // 使用构造函数创建数组

console.log(fruits[0]); // 输出:apple
console.log(numbers.length); // 输出:5

fruits.push("grape"); // 向数组末尾添加元素
console.log(fruits); // 输出:["apple", "banana", "orange", "grape"]

数组提供了丰富的方法和操作,例如排序、过滤、映射等。

  1. Function 函数:
    Function 是一种特殊的对象类型,可以定义和执行可重复使用的代码块。
javascript 复制代码
function add(a, b) {
  return a + b;
}

var sum = add(3, 5); // 调用函数并接收返回值
console.log(sum); // 输出:8

函数可以作为参数传递给其他函数,也可以作为对象的方法。

  1. Date 日期:
    Date 是用于表示日期和时间的引用类型。
javascript 复制代码
var currentDate = new Date(); // 创建当前日期对象

var specificDate = new Date(2022, 0, 1); // 创建指定日期对象

console.log(currentDate.getFullYear()); // 获取当前年份
console.log(specificDate.getMonth()); // 获取指定日期的月份

Date 对象提供了许多方法来获取和设置日期的各个部分,以及执行日期计算和格式化等操作。

块级作用域案例分析

块级作用域是由花括号 {} 包围的代码块,它限定了变量的作用范围。在 ES6(ECMAScript 2015)之前,JavaScript 中只有全局作用域和函数作用域,没有块级作用域。但是,通过使用 letconst 关键字引入的块级作用域可以在代码块内部创建新的作用域。

以下是一个案例分析,展示了块级作用域的使用场景:

javascript 复制代码
function calculateArea(radius) {
  if (radius > 0) {
    let pi = 3.14159;
    const area = pi * radius * radius;
    console.log("The area is: " + area);
  } else {
    console.log("Invalid radius.");
  }
}

calculateArea(5); // 输出:The area is: 78.53975
calculateArea(-2); // 输出:Invalid radius.

console.log(pi); // 报错:ReferenceError: pi is not defined
console.log(area); // 报错:ReferenceError: area is not defined

在上述案例中,piarea 变量都是在 if 代码块内部使用 letconst 声明的。这意味着这两个变量的作用域仅限于 if 代码块内部,超出该范围无法访问。这就是块级作用域的特性。

通过使用块级作用域,可以避免变量污染和命名冲突,同时提供更好的代码组织和封装。在需要限定变量作用范围的场景下,使用块级作用域可以带来更安全和可维护的代码。

var、let、const区别

作用域

var 声明的变量是全局作用域或函数作用域,而 let 和 const 声明的变量是块作用域。

重复声明

var 允许重复声明变量,而 let 和 const 不允许重复声明变量。

修改

var 声明的变量可以修改,而 let 和 const 声明的变量不能修改。

提升

var 和 let 声明的变量都会被提升到作用域的顶部,但 var 声明的变量会被赋予 undefined 值,而 let 声明的变量不会被初始化。

初始化

var 声明的变量可以初始化,也可以不初始化,而 let 和 const 声明的变量必须初始化。

示例

ini 复制代码
// 作用域
var a = 1; // 全局作用域
let b = 2; // 块作用域
const c = 3; // 块作用域

// 重复声明
var a = 2; // 全局作用域
let b = 2; // 块作用域
const c = 2; // 块作用域

// 修改
var a = 1;
a = 2; // 可以修改
let b = 2;
b = 3; // 可以修改
const c = 3;
// c = 4; // 报错:Cannot assign to read only property 'c' of const object

// 提升
var a;
console.log(a); // undefined
let b;
console.log(b); // undefined
const c;
console.log(c); // undefined

// 初始化
var a = 1; // 可以不初始化
let b = 2; // 可以不初始化
const c = 3; // 必须初始化

在实际的代码中,建议使用 let 和 const 声明变量,避免使用 var。

var声明前置

var 声明的变量存在变量提升(hoisting)的特性,即在作用域内的任何位置都可以访问到该变量,但其初始化值为 undefined。这意味着可以在变量声明之前使用变量。

例如:

ini 复制代码
console.log(name); // 输出:undefined
var name = "John";

在上述代码中,变量 name 在声明之前被访问,它的值为 undefined。这是因为在代码执行之前,JavaScript 引擎会将变量声明提升到作用域的顶部。

然而,虽然变量提升使得在声明之前使用变量成为可能,但是变量的赋值操作仍然会按照代码的顺序执行。因此,在声明之前访问变量会返回 undefined,直到变量被赋予实际的值。

需要注意的是,只有变量声明被提升,而不是赋值操作。因此,变量在被声明之前访问时,虽然不会引发错误,但它的值将是 undefined

推荐的做法是在使用变量之前先进行声明,以提高代码的可读性和可维护性。

function声明前置

函数声明也存在函数提升(function hoisting)的特性,与变量提升类似。这意味着可以在函数声明之前调用该函数。

例如:

scss 复制代码
sayHello(); // 输出:Hello!

function sayHello() {
  console.log("Hello!");
}

在上述代码中,函数 sayHello 在声明之前被调用,而不会引发错误。这是因为在代码执行之前,JavaScript 引擎会将函数声明提升到作用域的顶部。

与变量提升类似,函数声明的提升只是将函数声明本身提升到作用域的顶部,而不涉及函数内部的函数表达式或函数赋值。

需要注意的是,虽然函数声明可以在声明之前调用,但函数表达式不具有函数提升的特性。函数表达式必须在声明之后才能被调用。

推荐的做法是在代码中先声明函数,然后再调用函数,以提高代码的可读性和可维护性。

词法作用域

词法作用域(Lexical scope),也称为静态作用域,是一种作用域的工作方式,其中变量的可见性和访问权限在代码编写时就确定了,而不是在运行时确定。

在词法作用域中,作用域是由代码中变量和函数声明的位置决定的,而不是由代码的执行顺序决定的。每当函数被创建时,它就会在词法环境中保留对其父级作用域的引用。这意味着函数可以访问其定义时所处的作用域以及外部作用域中的变量。

以下是一个示例来说明词法作用域的概念:

javascript 复制代码
var name = "John";

function greet() {
  console.log("Hello, " + name + "!");
}

function sayHi() {
  var name = "Alice";
  greet();
}

sayHi(); // 输出:Hello, John!

在上述代码中,变量 name 在全局作用域中声明,并被赋值为 "John"。函数 greet 定义在全局作用域内,可以访问全局作用域中的变量 name

函数 sayHi 定义在全局作用域内,它在自己的作用域中声明了一个变量 name 并赋值为 "Alice"。然后它调用了函数 greet,在 greet 函数内部,它访问的是定义时的词法作用域,即全局作用域,因此输出的是 "Hello, John!"。

这个例子展示了词法作用域如何决定在函数内部访问的变量是哪一个。 无论函数在何处被调用,它都保留了对定义时的作用域的引用。

词法作用域的优点是在编写代码时可以准确地确定变量的可见性和访问权限,提高了代码的可读性和可维护性。

作用域链与案例

作用域链(Scope Chain)是在词法作用域中用于查找变量和函数的一种机制。当在一个作用域中引用一个变量或调用一个函数时,JavaScript 引擎会按照特定的规则来查找变量或函数的定义。作用域链是由当前作用域和所有外部嵌套作用域的变量对象组成的链式结构。

以下是一个案例来说明作用域链的概念

ini 复制代码
function outer() {
  var name = "John";

  function inner() {
    var message = "Hello, " + name + "!";
    console.log(message);
  }

  return inner;
}

var func = outer();
func(); // 输出:Hello, John!

在上述代码中,函数 outer 定义了一个局部变量 name,并创建了一个嵌套函数 inner。函数 inner 在其定义时可以访问外部作用域中的变量 name

当调用 outer 函数时,它返回了内部函数 inner 的引用,并将其赋值给变量 func。然后,通过调用 func(),可以执行内部函数 inner

在执行内部函数 inner 时,JavaScript 引擎会先在当前作用域中查找变量 name,如果找不到,它会向上一级作用域(即外部作用域)继续查找,直到找到变量的定义或达到全局作用域。这个查找过程形成了作用域链。

在本例中,内部函数 inner 在自己的作用域中找不到变量 name,它会沿着作用域链向上查找,在外部函数 outer 的作用域中找到了变量 name 的定义,因此可以正确访问并使用它。

作用域链的概念是理解 JavaScript 中作用域和变量查找的重要基础。它确保了变量和函数的可见性和访问性,使得内部函数可以访问外部作用域中的变量。

作用域、声明前置、TDZ案例

暂时性死域(temporal dead zone,简称 TDZ)是指在 let 或 const 声明变量之前,该变量在当前作用域中不可访问的区域。下面是一个结合作用域、声明前置和暂时性死区(Temporal Dead Zone,简称 TDZ)的案例:

javascript 复制代码
function example() {
  console.log(name); // 输出:undefined

  if (true) {
    var name = "John"; // var 声明的变量存在变量提升
    let age = 30; // let 声明的变量不存在变量提升

    console.log(name); // 输出:John
    console.log(age); // 输出:30
  }

  console.log(name); // 输出:John
  console.log(age); // 报错:ReferenceError: age is not defined
}

example();

在上述代码中,我们定义了一个函数 example。在函数体内,我们尝试在不同的作用域中声明和访问变量。

首先,在函数体内部的顶部,我们尝试访问变量 name,尽管它在此时还没有被声明,但由于使用的是 var 声明,它会被提升到函数作用域的顶部,并被赋予默认值 undefined。因此,第一个 console.log 语句输出 undefined

然后,我们进入一个 if 代码块。在该代码块内部,我们使用 var 声明了变量 name,并赋值为 "John"。由于变量提升的特性,该声明会被提升到代码块的顶部,所以在 console.log 语句中可以正确地访问到变量 name 的值,输出 "John"。同样,我们使用 let 声明了变量 age,由于 let 声明不存在变量提升,该声明只在 if 代码块内部有效。

在代码块外部,我们尝试访问变量 name,由于变量 name 是在外部作用域中声明的,它对整个函数体都是可见的,因此可以正确地访问到值 "John"。但是,我们尝试访问变量 age,由于它是使用 let 声明的,并且在当前作用域中不存在声明,所以会引发错误,抛出 ReferenceError: age is not defined

这个案例演示了变量提升、作用域以及暂时性死区的概念。var 声明的变量存在变量提升,可以在声明之前访问,但是其初始化值为 undefinedlet 声明的变量不存在变量提升,而且在声明之前访问会触发暂时性死区,导致 ReferenceError 错误。

相关推荐
小爱丨同学32 分钟前
宏队列和微队列
前端·javascript
沉登c1 小时前
Javascript客户端时间与服务器时间
服务器·javascript
持久的棒棒君1 小时前
ElementUI 2.x 输入框回车后在调用接口进行远程搜索功能
前端·javascript·elementui
小程xy4 小时前
react 知识点汇总(非常全面)
前端·javascript·react.js
非著名架构师5 小时前
js混淆的方式方法
开发语言·javascript·ecmascript
多多米10056 小时前
初学Vue(2)
前端·javascript·vue.js
敏编程6 小时前
网页前端开发之Javascript入门篇(5/9):函数
开发语言·javascript
看到请催我学习6 小时前
内存缓存和硬盘缓存
开发语言·前端·javascript·vue.js·缓存·ecmascript
XiaoYu20028 小时前
22.JS高级-ES6之Symbol类型与Set、Map数据结构
前端·javascript·代码规范
儒雅的烤地瓜8 小时前
JS | JS中判断数组的6种方法,你知道几个?
javascript·instanceof·判断数组·数组方法·isarray·isprototypeof