数据类型
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() 方法 |
|---|---|---|---|
| Number | number | [object Number] | number |
| String | string | [object String] | string |
| Boolean | boolean | [object Boolean] | boolean |
| undefined | undefined | [object Undefined] | undefined |
| null | object | [object Null] | null |
| Symbol | symbol | [object Symbol] | symbol |
| BigInt | bigint | [object BigInt] | bigint |
| Array | object | [object Array] | array |
| Object | object | [object Object] | object |
| Set | object | [object Set] | set |
| Map | object | [object Map] | map |
| Function | function | [object Function] | function |
| Generator | function | [object GeneratorFunction] | generator |
| Error | object | [object Error] | error |
| Date | object | [object Date] | date |
| Promise | object | [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