# 变量与数据类型
请使用 var 运算符声明变量。
# 标识符命名规范
# 命名规范
- 严格区分大小写;
- 命名必须以字母或
_
或$
开头,余下的部分可以是任意的字母,数字,或者是_
或者是$
; - 不能用关键字或者是保留字命名;
- javascript 自己的命名习惯;
- 驼峰命名法:getElementById
- 首字母大写:Object
- 命名要有意义。
# 关键字与保留字
- ECMA-262 定义了 ECMAScript 支持的一套关键字(keyword)、保留字(reserved word)。
- 关键字标识了 ECMAScript 语句的开头和/或结尾。根据规定,关键字是保留的,不能用作变量名或函数名。
- 保留字在某种意思上是为将来的关键字而保留的单词。因此保留字不能被用作变量名或函数名。
关键字保留字 | |||||
---|---|---|---|---|---|
abstract | arguments | boolean | break | byte | case |
catch | char | class | const | continue | debugger |
default | delete | do | double | else | enum |
eval | export | extends | false | final | finally |
float | for | function | goto | if | implements |
import | in | instanceof | int | interface | let |
long | native | new | null | package | private |
protected | public | return | short | static | super |
switch | synchronized | this | throws | transient | true |
try | typeof | var | void | volatile | while |
with | yield |
WARNING
如果将保留字用作变量名或函数名,那么除非将来的浏览器实现了该保留字,否则很可能收不到任何错误消息。当浏览器将其实现后,该单词将被看做关键字,如此将出现关键字错误。
var for = 123; //Uncaught SyntaxError: Unexpected token for
Copied!
# 变量
# 变量是什么?
WARNING
变量,就是一个用于存放数值的容器。
var name = "张三"; alert("hello "+name); // hello 张三 var num = 100; alert(num*10); // 1000
Copied!
TIP
变量是用来存储数值的。变量不是数值本身,它们仅仅是一个用于存储数值的容器。 你可以把变量想象成一个个用来装东西的纸箱子。

- 变量的独特之处在于它存放的数值是可以改变的。
- 变量它们能够存储任何的东西 -- 不只是字符串和数字。变量可以存储更复杂的数据,甚至是函数。
# 声明变量
JavaScript 有三种声明方式。
关键词 | 描述 | 版本 |
---|---|---|
var | 声明一个变量,可赋一个初始化值。 | ES5 |
let | 声明一个块作用域的局部变量,可赋一个初始化值。 | ES6 |
const | 声明一个块作用域的只读的命名常量。 | ES6 |
# 基本语法
声明一个变量的语法是在 var 关键字之后加上这个变量的名字:
var myName; var myAge;
Copied!
变量声明后,即可使用,以下通过输出变量方式来使用:
console.log(myName); //undefined console.log(myAge); //undefined
Copied!
以上这两个变量并没有赋值,他们是空的容器(在 Javascript 中用 undefined 表示,未定义)。
同时声明多个变量,通过,
隔开:
var myName,myAge,mySex;
Copied!
TIP
提示: 千万不要把两个概念弄混淆了,“一个变量存在,但是没有赋值”和“一个变量并不存在” — 他们完全是两回事.
//声明未赋值 var myName; console.log(myName); //输出undefined //未声明 console.log(firstName); //报错 firstName is not defined
Copied!
WARNING
用 var 或 let 语句声明的变量,如果没有赋初始值,则其值为 undefined。
# 初始化变量
初始化变量有两种方式:
- 先声明再初始化
- 声明变量并初始化
先声明再初始化,方法如下,在变量名之后跟上一个“=”,然后是值:
var myName; var myAge; myName="Nico"; myAge = 19; console.log(myName); //输出 Nico console.log(myAge); //输出 19
Copied!
【重点】声明变量并初始化:
var myName = "Nico"; var myAge = 19; console.log(myName); //输出 Nico console.log(myAge); //输出 19
Copied!
# 更新变量
一旦变量赋值,您可以通过简单地给它一个不同的值来更新它。
var myName = 'Nico'; console.log(myName); //输出 Nico myName = "Allcky"; console.log(myName); //输出 Allcky
Copied!
# [es6]常量(Constants)
你可以用关键字 const 创建一个只读的常量。
const PI = 3.141592654 ; console.log(PI); //输出 3.141592654
Copied!
- 常量不可以通过赋值改变其值,也不可以在脚本运行时重新声明。它必须被初始化为某个值。
- 常量的作用域规则与 let 块级作用域变量相同。若省略 const 关键字,则该标识符将被视为变量。
- 在同一作用域中,不能使用与变量名或函数名相同的名字来命名常量。
不能使用与变量名或函数名相同的名字来命名常量,例如:
function f() {}; const f = 5; //Uncaught SyntaxError: Identifier 'f' has already been declared //语法错误: 标识符 'f' 已经被声明
Copied!
常量标识符的命名规则和变量相同:必须以字母、下划线或美元符号开头并可以包含有字母、数字或下划线。
常量对象的属性是不受保护的,所以可以修改常量对象的属性,例如:
const MY_OBJECT = {"key": "value"}; MY_OBJECT.key = "otherValue";
Copied!
利用 ES6 中对象的方法
Object.freeze()
可将对象冻结,从而无法修改其属性 (详见Object对象
一节)
# 变量声明兼容性
let
与const
是 ES6 新增声明方式,其存在兼容问题:
# let
const
var
区别
# var
- var 声明作用域 关键的问题在于,使用 var 操作符定义的变量会成为包含它的函数的局部变量。比如,使用 var 在一个函数内部定义一个变量,就意味着该变量将在函数退出时被销毁:
function test() { var message = "hi"; // 局部变量 } test(); console.log(message); // 出错!
Copied!
这里,message 变量是在函数内部使用 var 定义的。函数叫 test(),调用它会创建这个变量并给 它赋值。调用之后变量随即被销毁,因此示例中的最后一行会导致错误。不过,在函数内定义变量时省 略 var 操作符,可以创建一个全局变量:
function test() { message = "hi"; // 全局变量 } test(); console.log(message); // "hi"
Copied!
去掉之前的 var 操作符之后,message 就变成了全局变量。只要调用一次函数 test(),就会定义 这个变量,并且可以在函数外部访问到。
- var 声明提升 使用 var 时,下面的代码不会报错。这是因为使用这个关键字声明的变量会自动提升到函数作用域 顶部:
function foo() { console.log(age); var age = 26; } foo(); // undefined // 之所以不会报错,是因为 ECMAScript 运行时把它看成等价于如下代码: function foo() { var age; console.log(age); age = 26; } foo(); // undefined
Copied!
function foo() { var age = 16; var age = 26; var age = 36; console.log(age); } foo(); // 36
Copied!
# let
let 跟 var 的作用差不多,但有着非常重要的区别。
最明显的区别是,let 声明的范围是块作用域, 而 var 声明的范围是函数作用域。
// var if (true) { var name = 'Matt'; console.log(name); // Matt } console.log(name); // Matt // let if (true) { let age = 26; console.log(age); // 26 } console.log(age); // ReferenceError: age 没有定义 // 重复声明 var name; var name; let age; let age; // SyntaxError;标识符 age 已经声明过了
Copied!
JavaScript 引擎会记录用于变量声明的标识符及其所在的块作用域,因此嵌套使用相同的标 识符不会报错,而这是因为同一个块中没有重复声明:
var name = 'Nicholas'; console.log(name); // 'Nicholas' if (true) { var name = 'Matt'; console.log(name); // 'Matt' } let age = 30; console.log(age); // 30 if (true) { let age = 26; console.log(age); // 26 }
Copied!
对声明冗余报错不会因混用 let 和 var 而受影响。这两个关键字声明的并不是不同类型的变量, 它们只是指出变量在相关作用域如何存在。
var name; let name; // SyntaxError let age; var age; // SyntaxError
Copied!
暂时性死区
let 与 var 的另一个重要的区别,就是 let 声明的变量不会在作用域中被提升。
// name 会被提升 console.log(name); // undefined var name = 'Matt'; // age 不会被提升 console.log(age); // ReferenceError:age 没有定义 let age = 26;
Copied!
在解析代码时,JavaScript 引擎也会注意出现在块后面的 let 声明,只不过在此之前不能以任何方 式来引用未声明的变量。在 let 声明之前的执行瞬间被称为“暂时性死区”(temporal dead zone),在此 阶段引用任何后面才声明的变量都会抛出 ReferenceError。
全局声明
与 var 关键字不同,使用 let 在全局作用域中声明的变量不会成为 window 对象的属性(var 声 明的变量则会)。
条件声明 在使用 var 声明变量时,由于声明会被提升,JavaScript 引擎会自动将多余的声明在作用域顶部合 并为一个声明。因为 let 的作用域是块,所以不可能检查前面是否已经使用 let 声明过同名变量,同 时也就不可能在没有声明的情况下声明它。
<script> var name = 'Nicholas'; let age = 26; </script> <script> // 假设脚本不确定页面中是否已经声明了同名变量 // 那它可以假设还没有声明过 var name = 'Matt'; // 这里没问题,因为可以被作为一个提升声明来处理 // 不需要检查之前是否声明过同名变量 let age = 36; // 如果 age 之前声明过,这里会报错 </script> // 使用 try/catch 语句或 typeof 操作符也不能解决,因为条件块中 let 声明的作用域仅限于该块。 <script> let name = 'Nicholas'; let age = 36; </script> <script> // 假设脚本不确定页面中是否已经声明了同名变量 // 那它可以假设还没有声明过 if (typeof name === 'undefined') { let name; } // name 被限制在 if {} 块的作用域内 // 因此这个赋值形同全局赋值 name = 'Matt'; try { console.log(age); // 如果 age 没有声明过,则会报错 } catch(error) { let age; } // age 被限制在 catch {}块的作用域内 // 因此这个赋值形同全局赋值 age = 26; </script>
Copied!
var name = 'Matt'; console.log(window.name); // 'Matt' let age = 26; console.log(window.age); // undefined
Copied!
const
const实际上保证的,并不是变量的值不得改动,而是变量指向的那个内存地址所保存的数据不得改动。对于简单类型的数据(数值、字符串、布尔值),值就保存在变量指向的那个内存地址,因此等同于常量。但对于复合类型的数据(主要是对象和数组),变量指向的内存地址,保存的只是一个指向实际数据的指针,const只能保证这个指针是固定的(即总是指向另一个固定的地址),至于它指向的数据结构是不是可变的,就完全不能控制了。因此,将一个对象声明为常量必须非常小心。
const foo = {}; // 为 foo 添加一个属性,可以成功 foo.prop = 123; foo.prop // 123 // 将 foo 指向另一个对象,就会报错 foo = {}; // TypeError: "foo" is read-only
Copied!
ES6 声明变量的六种方法
ES5 只有两种声明变量的方法:var命令和function命令。ES6 除了添加let和const命令,后面章节还会提到,另外两种声明变量的方法:import命令和class命令。所以,ES6 一共有 6 种声明变量的方法
# 数据类型
计算机顾名思义就是可以做数学计算的机器,因此,计算机程序理所当然地可以处理各种数值。但是,计算机能处理的远不止数值,还可以处理文本、图形、音频、视频、网页等各种各样的数据,不同的数据,需要定义不同的数据类型。
在 JavaScript 中定义了以下几种数据类型:
- ECMAScript 有 6 种初始类型(primitive type),即
Undefined
、Null
、Boolean
、Number
、String
和 ES6 新增的Symbol
以及 ES2020新增的数据类型
。 - ECMAScript 有 1 种引用类型,即
Object
分类 | 类型 | 值 |
---|---|---|
初始类型 | undefined | undefined |
null | 一个表明 null 值的特殊关键字。 JavaScript 是大小写敏感的,因此 null 与 Null、NULL或其他变量完全不同。 | |
string | 单双引号引起来的数据 | |
number | 包括整型和浮点型。科学计数法。支持二进制、八进制、十进制、十六进制。 | |
BigInt(大整数) | ES2020 引入了一种新的数据类型 BigInt(大整数),这是 ECMAScript 的第八种数据类型。BigInt 只用来表示整数,没有位数的限制,任何位数的整数都可以精确表示。 | |
boolean | true false | |
Symbol | 表示独一个一无二的值。(ES6新增) | |
引用类型 | Object | 包含相关属性和方法的一个集合。包含Function、Array、Object |
# undefined
undefined 未定义,指的就是变量创建后但是没有赋值,而变量的默认值就是undefined
.
如:
var a; console.log(a); // undefined
Copied!
# null
null 一个表明 null 值的特殊关键字,相当于一个占位符。JavaScript 是大小写敏感的,因此 null 与 Null、NULL 或其他变量完全不同。
Null 类型同样只有一个值,即特殊值 null。逻辑上讲,null 值表示一个空对象指针,这也是给 typeof 传一个 null 会返回"object"的原因:
let car = null; console.log(typeof car); // "object"
Copied!
在定义将来要保存对象值的变量时,建议使用 null 来初始化,不要使用其他值。这样,只要检查 这个变量的值是不是 null 就可以知道这个变量是否在后来被重新赋予了一个对象的引用,比如:
if (car != null) { // car 是一个对象的引用 }
Copied!
undefined 值是由 null 值派生而来的,因此 ECMA-262 将它们定义为表面上相等,如下面的例 子所示:
console.log(null == undefined); // true
Copied!
用等于操作符(==)比较 null 和 undefined 始终返回 true。但要注意,这个操作符会为了比较 而转换它的操作数 即使 null 和 undefined 有关系,它们的用途也是完全不一样的。如前所述,永远不必显式地将 变量值设置为 undefined。但 null 不是这样的。任何时候,只要变量要保存对象,而当时又没有那个 对象可保存,就要用 null 来填充该变量。这样就可以保持 null 是空对象指针的语义,并进一步将其 与 undefined 区分开来。 null 是一个假值。因此,如果需要,可以用更简洁的方式检测它。不过要记住,也有很多其他可 能的值同样是假值。所以一定要明确自己想检测的就是 null 这个字面值,而不仅仅是假值。
let message = null; let age; if (message) { // 这个块不会执行 } if (!message) { // 这个块会执行 } if (age) { // 这个块不会执行 } if (!age) { // 这个块会执行 }
Copied!
# string 类型
用单双引号来说明,他所包围的值都可以是字符串。
引号包裹的字符串中间不允许换行
单双引号的用法:
- 效率是一样的
- 只能成对出现,不能相互交叉使用
- 可以相互嵌套。
还包括一些特殊的字符:
字符 | 描述 |
---|---|
\0 | Null 字节 |
\b | 退格符 |
\f | 换页符 |
\n | 换行符 |
\r | 回车符 |
\t | Tab (制表符) |
\v | 垂直制表符 |
' | 单引号 |
" | 双引号 |
\ | 反斜杠字符(\) |
\XXX | 由从 0 到 377 最多三位八进制数 XXX 表示的 Latin-1 字符。例如,\251 是版权符号的八进制序列。 |
\xXX | 由从 00 和 FF 的两位十六进制数字 XX 表示的 Latin-1 字符。例如,\ xA9 是版权符号的十六进制序列。 |
\uXXXX | 由四位十六进制数字 XXXX 表示的 Unicode 字符。例如,\ u00A9 是版权符号的 Unicode 序列。见 Unicode escape sequences (Unicode 转义字符). |
\u{XXXXX} | Unicode 代码点 (code point) 转义字符。例如,\u{2F804} 相当于 Unicode 转义字符 \uD87E\uDC04 的简写。 |
# number 类型
number 类型包括整型和浮点型。支持十进制(基数为 10)、十六进制(基数为 16)、八进制(基数为 8)以及二进制(基数为 2)表示。
进制 | 表示 |
---|---|
十进制 | 十进制整数字面量由一串数字序列组成,且没有前缀 0。 |
八进制 | 八进制的整数以 0(或 0O、0o)开头,只能包括数字 0-7。 |
十六进制 | 十六进制整数以 0x(或 0X)开头,可以包含数字(0-9)和字母 a~f 或 A~F。 |
二进制 | 二进制整数以 0b(或 0B)开头,只能包含数字 0 和 1。 |
var n1 = 100; //十进制 var n2 = 071; //八进制 var n3 = 0o71; //八进制 ES6规定 0o开头 var n4 = 0xa9; //十六进制 var n5 = 0b11; //二进制
Copied!
严格模式下,八进制整数字面量必须以 0o 或 0O 开头,而不能以 0 开头。
对于非常大或非常小的数,可以用科学计数法表示,可以把一个数表示为数字(包括十进制数字)加 e(或 E),后面加乘以 10 的倍数。例如:
# 用科学计数法来表示,还包括一些特殊的值: # 最大值 Number.MAX_VALUE //1.7976931348623157e+308 # 最小值 Number.MIN_VALUE //5e-324 var n1 = 3e2; //3*100 = 300 var n2 = 5e3; //5*1000 = 5000 var n3 = 5.3e3; //5.3*1000 = 5300 # 无穷大 Infinity // 1/0 = Infinity
Copied!
# BigInt 类型 ES2020
TIP
JavaScript 所有数字都保存成 64 位浮点数,这给数值的表示带来了两大限制。一是数值的精度只能到 53 个二进制位(相当于 16 个十进制位),大于这个范围的整数,JavaScript 是无法精确表示的,这使得 JavaScript 不适合进行科学和金融方面的精确计算。二是大于或等于2的1024次方的数值,JavaScript 无法表示,会返回Infinity。
// 超过 53 个二进制位的数值,无法保持精度 Math.pow(2, 53) === Math.pow(2, 53) + 1 // true // 超过 2 的 1024 次方的数值,无法表示 Math.pow(2, 1024) // Infinity
Copied!
ES2020 引入了一种新的数据类型 BigInt(大整数),来解决这个问题,这是 ECMAScript 的第八种数据类型。BigInt 只用来表示整数,没有位数的限制,任何位数的整数都可以精确表示。
const a = 2172141653n; const b = 15346349309n; // BigInt 可以保持精度 a * b // 33334444555566667777n // 普通整数无法保持精度 Number(a) * Number(b) // 33334444555566670000
Copied!
TIP
为了与 Number 类型区别,BigInt 类型的数据必须添加后缀n。
1234 // 普通整数 1234n // BigInt // BigInt 的运算 1n + 2n // 3n
Copied!
BigInt 同样可以使用各种进制表示,都要加上后缀n
。
0b1101n // 二进制 0o777n // 八进制 0xFFn // 十六进制
Copied!
BigInt 与普通整数是两种值,它们之间并不相等。
42n === 42 // false
Copied!
typeof
运算符对于 BigInt 类型的数据返回bigint
。
typeof 123n // 'bigint'
Copied!
BigInt 可以使用负号(-
),但是不能使用正号(+
),因为会与 asm.js 冲突。
-42n // 正确 +42n // 报错
Copied!
JavaScript 以前不能计算70的阶乘(即70!
),因为超出了可以表示的精度。
let p = 1; for (let i = 1; i <= 70; i++) { p *= i; } console.log(p); // 1.197857166996989e+100
Copied!
现在支持大整数了,就可以算了,浏览器的开发者工具运行下面代码,就OK。
let p = 1n; for (let i = 1n; i <= 70n; i++) { p *= i; } console.log(p); // 11978571...00000000n
Copied!
# boolean 类型
Boolean. 布尔值,true 和 false. 表示"真"和"假"
# Symbol es6
Symbol ( 在 ECMAScript 6 中新添加的类型).。一种数据类型,它的实例是唯一且不可改变的。凡是属性名属于 Symbol 类型,就都是独一无二的,可以保证不会与其他属性名产生冲突。
# Object
引用类型: 引用类型是一种数据结构,用于将数据和功能组织在一起,存储大量的数据
三大引用类型: Object 、Array 、Function
# 判断数据类型
当我们想知道一个变量中存储的数据类型是什么,我们可以通过 typeof 运算符。
# 语法
typeof
运算符后跟操作数:
typeof operand; // or typeof operand;
Copied!
# 返回值
下表总结了typeof
可能的返回值
类型 | 结果 |
---|---|
Undefined | "undefined" |
Null | "object" |
Boolean | "boolean" |
Number | "number" |
String | "string" |
Symbol | "symbol" |
BigInt | "bigint" |
函数对象 | "function" |
任何其他对象 | "object" |
WARNING
注意 严格来讲,函数在 ECMAScript 中被认为是对象,并不代表一种数据类型。可是, 函数也有自己特殊的属性。为此,就有必要通过 typeof 操作符来区分函数和其他对象。
# 示例
// typeof总是返回一个字符串 typeof typeof 1 === "string"; // Number typeof 37 === "number"; typeof NaN === "number"; typeof Math.PI === "number"; // String typeof "" === "string"; typeof "bla" === "string"; // Object typeof { a: 1 } === "object"; typeof [1, 2, 4] === "object"; typeof null === "object";
Copied!
# 变量在内存划分
变量类型分两种是由于其在内存中存储方式不同:基本类型的数据是存放在
栈
内存中的,而引用类型的数据是存放在堆
内存中的
# 堆栈的概念
两者都是存放临时数据的地方。
# 栈
栈(数据结构):一种先进后出的数据结构。
栈区(stack) 由编译器自动分配释放 ,存放函数的参数值,局部变量的值等。
这种乒乓球的存放方式与栈中存取数据的方式如出一辙。处于盒子中最顶层的乒乓球 5,它一定是最后被放进去,但可以最先被使用。而我们想要使用底层的乒乓球 1,就必须将上面的 4 个乒乓球取出来,让乒乓球 1 处于盒子顶层。这就是栈空间先进后出,后进先出的特点。图中已经详细的表明了栈空间的存储原理。
# 堆
堆(数据结构):堆可以被看成是一棵树,如:堆排序;
堆区(heap) 一般由程序员分配释放,若程序员不释放,程序结束时可能由 OS 回收。
堆数据结构是一种树状结构。它的存取数据的方式,则与书架与书非常相似。
书虽然也整齐的存放在书架上,但是我们只要知道书的名字,我们就可以很方便的取出我们想要的书,而不用像从乒乓球盒子里取乒乓一样,非得将上面的所有乒乓球拿出来才能取到中间的某一个乒乓球。好比在 JSON 格式的数据中,我们存储的 key-value 是可以无序的,因为顺序的不同并不影响我们的使用,我们只需要关心书的名字
堆是在程序运行时,而不是在程序编译时,申请某个大小的内存空间。即动态分配内存,对其访问和对一般内存的访问没有区别。对于堆,我们可以随心所欲的进行增加变量和删除变量,不用遵循次序。
# 两种数据类型的存放
# 栈区存储
JavaScript 的基础数据类型往往都会保存在变量对象中,即保存在栈内存中,因为这些类型在内存中分别占有固定大小的空间,通过按值来访问。
基本类型由于数据简单,会存放到内存栈区,栈区包括了 变量的标识符和变量的值:
栈区: 长度固定,读取速度快
# 堆区存储
JS 的引用数据类型,比如数组 Array,Object,它们值的大小是不固定的。引用数据类型的值是保存在堆内存中的对象。JavaScript 不允许直接访问堆内存中的位置,因此我们不能直接操作对象的堆内存空间。在操作对象时,实际上是在操作对象的引用而不是实际的对象。因此,引用类型的值都是按引用访问的。这里的引用,我们可以理解为保存在变量对象中的一个地址,该地址与堆内存的实际值相关联。
我们可以结合以下例子与图解进行理解:
堆区: 长度不固定,读取速度慢
# 变量复制
# 基本数据类型
基本数据类型:基本数据类型值指保存在栈内存中的简单数据段。访问方式是按值访问。
var a = 1;
Copied!
操作的是变量实际保存的值。
a = 2;
Copied!
基本类型变量的复制:从一个变量向一个变量复制时,会在栈中创建一个新值,然后把值复制到为新变量分配的位置上。
var b = a;
Copied!
b = 2;
Copied!
# 引用数据类型
引用数据类型:引用数据类型值指保存在堆内存中的对象。也就是,变量中保存的实际上的只是一个指针,这个指针指向内存中的另一个位置,该位置保存着对象。访问方式是按引用访问。
var a = new Object();
Copied!
当操作时,需要先从栈中读取内存地址,然后再延指针找到保存在堆内存中的值再操作。
a.name = 'xz';
Copied!
引用类型变量的复制:复制的是存储在栈中的指针,将指针复制到栈中未新变量分配的空间中,而这个指针副本和原指针指向存储在堆中的同一个对象;复制操作结束后,两个变量实际上将引用同一个对象。因此,在使用时,改变其中的一个变量的值,将影响另一个变量。
var b = a;
Copied!
b.sex = 'boy';
Copied!
console.log(a.sex) //boy console.log(b.sex) //boy
Copied!
← ECMAScript介绍 运算符 →