MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

JavaScript中的数据类型与变量声明

2024-01-112.3k 阅读

JavaScript 中的数据类型

在 JavaScript 编程世界里,数据类型是基石,它们决定了数据的存储方式、操作行为以及程序运行的逻辑。JavaScript 拥有丰富的数据类型,大致可分为两类:基本数据类型和引用数据类型。

基本数据类型

1. 数值(Number)

JavaScript 中的数值类型涵盖了整数和浮点数。与其他一些语言不同,JavaScript 没有专门区分整数类型和浮点类型,所有数字在内部都以 64 位双精度浮点格式存储。这意味着在 JavaScript 中,无论是 1 还是 1.0,本质上是相同的数据类型。

let integer = 5;
let floatingPoint = 3.14;

JavaScript 支持多种数值表示法,除了十进制,还支持二进制(前缀 0b0B)、八进制(前缀 0o0O,在严格模式下,不允许无前缀的八进制表示)和十六进制(前缀 0x0X)。

let binaryNumber = 0b1010; // 二进制 1010 转换为十进制是 10
let octalNumber = 0o755; // 八进制 755 转换为十进制是 493
let hexadecimalNumber = 0xFF; // 十六进制 FF 转换为十进制是 255

数值类型还有一些特殊值,例如 NaN(Not a Number),它表示一个非法的或未定义的数值运算结果。NaN 与任何值(包括它自身)都不相等,判断一个值是否为 NaN 应使用 isNaN() 函数。

let result = 1 / 'a';
console.log(isNaN(result)); // true

Infinity-Infinity 表示无穷大与负无穷大,通常是由于数值运算溢出导致。

let largeNumber = 1e1000; // Infinity
let negativeLargeNumber = -1e1000; // -Infinity

2. 字符串(String)

字符串是由零个或多个 Unicode 字符组成的序列,用单引号(')、双引号(")或反引号(```)括起来。

let singleQuoted = 'Hello, world!';
let doubleQuoted = "Hello, world!";
let templateLiteral = `Hello, world!`;

反引号创建的字符串称为模板字面量,它允许嵌入表达式,使用 ${} 语法。

let name = 'John';
let greeting = `Hello, ${name}!`;
console.log(greeting); // Hello, John!

字符串是不可变的,一旦创建,其内容不能改变。任何看似修改字符串的操作实际上都会返回一个新的字符串。

let str = 'Hello';
let newStr = str + ', world';
console.log(str); // Hello
console.log(newStr); // Hello, world

JavaScript 提供了许多字符串操作方法,如 length 属性获取字符串长度,charAt() 获取指定位置的字符,indexOf() 查找子字符串位置等。

let str = 'JavaScript';
console.log(str.length); // 10
console.log(str.charAt(4)); // 'S'
console.log(str.indexOf('va')); // 1

3. 布尔值(Boolean)

布尔值只有两个取值:truefalse,常用于逻辑判断。

let isDone = true;
let hasError = false;

在条件判断中,许多值会被自动转换为布尔值。例如,0''(空字符串)、nullundefinedNaN 会被转换为 false,其他大多数值(包括非零数字、非空字符串等)会被转换为 true

if (0) {
    console.log('This will not be printed');
}
if ('hello') {
    console.log('This will be printed');
}

4. null

null 表示一个空值,它是 JavaScript 基本数据类型之一。通常用于表示有意的空缺值,比如提前知道某个变量未来会被赋值,但目前还没有值。

let emptyValue = null;

在进行类型检查时,null 的类型是 object,这是 JavaScript 的一个历史遗留问题。可以使用 === 严格相等运算符来判断一个值是否为 null

let value = null;
console.log(value === null); // true

5. undefined

undefined 表示变量声明了但未赋值的状态。当访问对象不存在的属性,或者函数调用没有返回值时,也会得到 undefined

let variable;
console.log(variable); // undefined

let obj = {};
console.log(obj.nonexistentProperty); // undefined

function noReturnValueFunction() {}
let result = noReturnValueFunction();
console.log(result); // undefined

null 不同,null 是有意设置为空值,而 undefined 更多表示一种未初始化的状态。nullundefined 在松散相等(==)时相等,但在严格相等(===)时不相等。

console.log(null == undefined); // true
console.log(null === undefined); // false

6. Symbol

Symbol 是 ES6 引入的一种新的基本数据类型。每个 Symbol 值都是唯一的,它用于创建对象的唯一属性键,避免属性名冲突。

let sym1 = Symbol('description');
let sym2 = Symbol('description');
console.log(sym1 === sym2); // false

Symbol 类型的值不能与其他类型的值进行运算,也不能被自动转换为字符串。可以使用 Symbol.prototype.toString() 方法将其转换为字符串。

let sym = Symbol('test');
console.log(sym.toString()); // Symbol(test)

引用数据类型

对象(Object)

对象是 JavaScript 中最复杂也是最常用的引用数据类型。它是一个无序的键值对集合,其中键是字符串(ES6 之后也可以是 Symbol),值可以是任意数据类型,包括其他对象。

let person = {
    name: 'Alice',
    age: 30,
    hobbies: ['reading', 'painting']
};

对象的属性可以通过点号(.)或方括号([])来访问。当属性名包含特殊字符或动态生成时,方括号表示法更为适用。

console.log(person.name); // Alice
console.log(person['age']); // 30

let dynamicKey = 'hobbies';
console.log(person[dynamicKey]); // ['reading', 'painting']

对象属性可以动态添加和删除。使用 delete 关键字可以删除对象的属性。

person.gender = 'female';
console.log(person.gender); // female

delete person.gender;
console.log(person.gender); // undefined

对象也可以通过构造函数创建。例如,Object 构造函数可以创建一个空对象,也可以接受一个对象字面量作为参数来创建具有初始属性的对象。

let emptyObject = new Object();
let anotherPerson = new Object({
    name: 'Bob',
    age: 25
});

数组(Array)

数组是一种特殊的对象,它用于存储有序的元素集合。数组的元素可以是任意数据类型,并且数组的长度是可变的。

let numbers = [1, 2, 3, 4, 5];
let mixedArray = ['hello', 10, true, null];

数组元素可以通过索引访问,索引从 0 开始。

console.log(numbers[2]); // 3

数组有许多内置方法,如 push() 在数组末尾添加元素,pop() 删除并返回数组的最后一个元素,shift() 删除并返回数组的第一个元素,unshift() 在数组开头添加元素等。

let fruits = ['apple', 'banana'];
fruits.push('cherry');
console.log(fruits); // ['apple', 'banana', 'cherry']

let removedFruit = fruits.pop();
console.log(removedFruit); // cherry
console.log(fruits); // ['apple', 'banana']

let firstFruit = fruits.shift();
console.log(firstFruit); // apple
console.log(fruits); // ['banana']

fruits.unshift('kiwi');
console.log(fruits); // ['kiwi', 'banana']

数组的长度可以通过 length 属性获取和修改。修改 length 属性可以截断或扩展数组。

let arr = [1, 2, 3, 4, 5];
console.log(arr.length); // 5

arr.length = 3;
console.log(arr); // [1, 2, 3]

arr.length = 5;
console.log(arr); // [1, 2, 3, empty, empty]

函数(Function)

在 JavaScript 中,函数是一等公民,这意味着函数可以像其他数据类型一样被赋值给变量、作为参数传递给其他函数、从其他函数返回等。函数也是一种特殊的对象,它具有可执行的代码块。

function add(a, b) {
    return a + b;
}

let sum = add(3, 5);
console.log(sum); // 8

函数可以作为变量的值进行赋值。

let greet = function() {
    console.log('Hello!');
};
greet(); // Hello!

函数还可以作为参数传递给其他函数,这种函数被称为回调函数。

function doMath(a, b, callback) {
    return callback(a, b);
}

function multiply(a, b) {
    return a * b;
}

let result = doMath(4, 5, multiply);
console.log(result); // 20

函数也可以返回另一个函数,这种函数被称为高阶函数。

function makeAdder(x) {
    return function(y) {
        return x + y;
    };
}

let add5 = makeAdder(5);
let sum = add5(3);
console.log(sum); // 8

JavaScript 中的变量声明

变量在 JavaScript 中用于存储数据值,通过变量声明,我们可以给数据值命名,以便在程序的不同部分引用和操作这些数据。JavaScript 提供了多种变量声明方式,每种方式都有其特点和适用场景。

var 声明

var 是 JavaScript 早期用于声明变量的关键字。它具有函数作用域,而不是块级作用域。这意味着在函数内部声明的 var 变量在整个函数内都可以访问,即使是在声明之前访问(这种现象称为变量提升)。

function varScopeExample() {
    console.log(x); // undefined
    var x = 10;
    console.log(x); // 10
}
varScopeExample();

在上述例子中,x 虽然在 console.log(x) 之后声明,但由于变量提升,它在整个函数内都有定义,只是在声明之前其值为 undefined

var 声明的变量还存在一个问题,就是在循环中使用时可能会导致意外的结果。因为 var 的作用域是函数级别的,而不是块级别的。

for (var i = 0; i < 5; i++) {
    setTimeout(() => {
        console.log(i);
    }, 1000);
}
// 预期输出 0, 1, 2, 3, 4
// 实际输出 5, 5, 5, 5, 5

在这个例子中,由于 var i 的作用域是整个函数,当 setTimeout 回调函数执行时,循环已经结束,i 的值变为 5

let 声明

let 是 ES6 引入的用于声明变量的关键字,它具有块级作用域。这意味着在块(由 {} 包围的代码区域)内声明的 let 变量只在该块内有效。

{
    let y = 20;
    console.log(y); // 20
}
console.log(y); // ReferenceError: y is not defined

在循环中使用 let 声明变量可以避免 var 带来的问题。

for (let j = 0; j < 5; j++) {
    setTimeout(() => {
        console.log(j);
    }, 1000);
}
// 输出 0, 1, 2, 3, 4

这里 let j 的作用域是每次循环的块,每个 setTimeout 回调函数捕获的是不同的 j 值。

let 声明的变量也存在变量提升,但与 var 不同的是,let 变量在声明之前处于“暂时性死区”,在这个区域访问变量会导致 ReferenceError

console.log(z); // ReferenceError: z is not defined
let z = 30;

const 声明

const 同样是 ES6 引入的关键字,用于声明常量。常量一旦声明,其值就不能再改变。与 let 一样,const 具有块级作用域。

const PI = 3.14159;
// PI = 3.14; // TypeError: Assignment to constant variable.

对于引用数据类型(如对象和数组),使用 const 声明只是保证变量所指向的内存地址不变,而对象或数组内部的属性或元素是可以修改的。

const obj = {
    value: 10
};
obj.value = 20;
console.log(obj.value); // 20

const arr = [1, 2, 3];
arr.push(4);
console.log(arr); // [1, 2, 3, 4]

如果想要确保对象或数组的内容不可变,可以使用 Object.freeze() 方法冻结对象,或者使用 Object.seal() 方法密封对象(密封对象不能添加新属性,但可以修改现有属性)。

const frozenObj = Object.freeze({
    value: 10
});
// frozenObj.value = 20; // 严格模式下会报错,非严格模式下不报错但修改无效

变量声明的最佳实践

在现代 JavaScript 编程中,推荐优先使用 const 声明变量,因为它可以提高代码的可读性和可维护性,避免意外的变量值修改。只有当变量的值需要改变时,才使用 let。尽量避免使用 var,除非是在兼容旧代码的场景下。

在声明变量时,应遵循良好的命名规范,变量名应具有描述性,能清晰地表达变量所代表的数据含义。例如,使用 userName 而不是 u 来表示用户名。

同时,尽量将变量声明放在作用域的顶部,这样可以提高代码的清晰度,避免因变量提升带来的意外行为。对于复杂的逻辑,合理使用块级作用域来限制变量的作用范围,有助于减少命名冲突和代码的复杂度。

总之,理解 JavaScript 中的数据类型和变量声明方式是编写高效、健壮 JavaScript 代码的基础,熟练掌握它们可以让开发者更好地控制程序的行为和数据的管理。在实际开发中,应根据具体的需求和场景,选择最合适的数据类型和变量声明方式,以实现最佳的编程效果。