✊不积跬步,无以至千里;不积小流,无以成江海。
引用类型与案例分析
引用类型是一种用于存储和操作复杂数据的数据类型。与基本数据类型(如数字、字符串等)不同,引用类型是由多个值组成的对象,可以包含属性和方法。以下是几种常见的引用类型及其案例分析:
- 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";
对象的属性可以通过点语法或方括号语法进行访问和修改。
- 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"]
数组提供了丰富的方法和操作,例如排序、过滤、映射等。
- Function 函数:
Function 是一种特殊的对象类型,可以定义和执行可重复使用的代码块。
javascript
function add(a, b) {
return a + b;
}
var sum = add(3, 5); // 调用函数并接收返回值
console.log(sum); // 输出:8
函数可以作为参数传递给其他函数,也可以作为对象的方法。
- 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 中只有全局作用域和函数作用域,没有块级作用域。但是,通过使用 let
和 const
关键字引入的块级作用域可以在代码块内部创建新的作用域。
以下是一个案例分析,展示了块级作用域的使用场景:
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
在上述案例中,pi
和 area
变量都是在 if
代码块内部使用 let
和 const
声明的。这意味着这两个变量的作用域仅限于 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
声明的变量存在变量提升,可以在声明之前访问,但是其初始化值为 undefined
。let
声明的变量不存在变量提升,而且在声明之前访问会触发暂时性死区,导致 ReferenceError
错误。