变量与数据类型
变量
变量的默认值
一个变量只定义,但没有赋初值,默认值是
undefined
一个变量只有被
var
定义,并赋初值之后,才算正式初始化完成
变量的常见错误
注意
虽然可以通过省略 var
操作符定义全局变量,但不推荐这么做。在局部作用域中定义的全局变量很难维护,也会造成困惑。这是因为不能一下子断定省略 var
是不是有意而为之。在严格模式下,如果像这样给未声明的变量赋值,则会导致抛出错误
- 不用
var
定义,而直接将值赋予它,虽不引发报错,但会产生作用域问题:
在函数外,用 var
声明的变量为全局变量,不用 var
声明的变量也为全局变量。全局变量其实是在 window
对象中添加属性并赋值
var a = 123 // 使用var声明
b = 456 // 不使用var声明
console.log(a) // 123
console.log(b) // 456
console.log(window.a) // 123
console.log(window.b) // 456
在函数中,用 var
声明的变量为局部变量,不用 var
声明的变量为全局变量
function fn() {
var a = 123 // 使用var声明
b = 456 // 不使用var声明
console.log(a) // 123
console.log(b) // 456
console.log(window.a) // undefined
console.log(window.b) // 456
}
fn()
console.log(b) // 456
delete
用来删除对象的属性,如果是不能删除的属性返回 false
,其他情况返回 true
,可以看到,变量 a
b
都是全局变量,同为 window
对象的其中一个属性,a
不可以删除,b
可以删除
var a = 123 // 使用var声明
b = 456 // 不使用var声明
console.log(window.a) // 123
console.log(window.b) // 456
console.log(delete a) // false
console.log(delete b) // true
console.log(window.a) // 123
console.log(window.b) // undefined
即:同为全局变量,同为 window
对象的其中一个属性,用 var
声明的变量不可以删除,不用 var
声明的变量可以删除!
对象属性是否可删除, 其实是可以配置的:
Object.getOwnPropertyDescriptor()
方法返回某个对象属性的描述对象
var a = 123 // 使用var声明
b = 456 // 不使用var声明
console.log(Object.getOwnPropertyDescriptor(window, 'a'))
// {value: 123, writable: true, enumerable: true, configurable: false}
console.log(Object.getOwnPropertyDescriptor(window, 'b'))
// {value: 456, writable: true, enumerable: true, configurable: true}
我们可以看到 window
对象的属性 a
和 b
的描述对象包含以下信息:
value
属性的值writable
属性是否可被修改,布尔值enumerable
属性是否可被枚举(遍历),布尔值configurable
属性是否可以被删除,布尔值- 属性
a
和b
的描述对象区别在于是否可删除, 这个特性属性a
为configurable: false
不可删除,属性b
为configurable: true
可删除
即:对象的属性是否可删除,取决于描述对象的属性 configurable
,用 var
声明的变量默认不可删除,不用 var
声明的变量默认可删除
经过下面代码在浏览器中测试,var 定义的全局变量,无法修改它的 configurable
属性,即它无法被修改为可删除。而不使用 var
声明的全局变量可以修改它的 configurable
属性:
var a = 123 // 使用var声明
b = 456 // 不使用var声明
Object.defineProperty(window, 'b', { configurable: false }) // 默认为true
console.log(delete b) // fasle
Object.defineProperty(window, 'a', { configurable: true }) //无法重新定义属性: a at Function.defineProperty
- 尝试使用一个既没有被
var
定义过,也没有赋过值的字符就会产生引用错误。
console.log(b) // b is not defined
变量的合法命名
提示
变量,函数,对象的属性 一般都采用小驼峰命名法,类名,构造函数采用大驼峰命名法,即首字母也要大写
关键字、保留字、true
、false
和 null
,undefined
不能作为标识符
标识符
就是变量,函数,属性或函数参数的名称
只能由 字母、数字、下划线、$ 组成,但 不能 以数字开头
不能是关键字或保留字
大小写敏感,
a
和A
两个不同的变量
变量声明的提升
你可以提前使用一个稍后才声明的变量,而不会引发异常
变量声明提升时,只提升变量的定义到当前作用域(全局作用域或者函数作用域)的顶部,不会提升它的值
- 全局作用域:
console.log(b) // 输出undefined
var b = 12
- 函数作用域:
var a = 123
// 函数作用域
function fun() {
console.log(a)
// var 声明的变量没有块级作用域
if (false) {
var a = 456
}
}
fun() // undefined
看道题
下列代码输出结果是?
var val = 12
function fun1() {
console.log(val)
var val = 20
console.log(val)
}
fun1()
// undefined
// 20
JS 基本数据类型
数据类型分类
- 基本数据类型:
string
,boolean
,number
,symbol
(ES6 新增),undefined
,null
- 引用数据类型:
object
- js 的常见内置对象:
Date
,Array
,Math
,Number
,Boolean
,String
,Array
,RegExp
,Function
...
提示
其中 Symbol
是 ES6
中新增的数据类型:
Symbol
符号是原始值,且符号实例是唯一、不可变的。 符号的用途是确保对象属性使用唯一标识符,不会发生属性冲突的危险。
更多 Symbol
细节见 ES6
章节
基本数据类型和引用数据类型的区别
内存的分配不同
- 基本数据类型存储在栈中
- 复杂数据类型存储在堆中,栈中存储的变量,是指向堆中的引用地址
访问机制不同
- 基本数据类型是按值访问
- 复杂数据类型按引用访问,
JS
不允许直接访问保存在堆内存中的对象,在访问一个对象时,首先得到的是这个对象在堆内存中的地址,然后再按照这个地址去获得这个对象的值
复制变量时不同a=b
基本数据类型:
a=b
;是将b
中保存的原始值的副本赋值给新变量a
,a
和b
完全独立,互不影响复杂数据类型:
a=b
;将b
保存的对象内存的引用地址赋值给了新变量a
;a
和b
指向了同一个堆内存地址,其中一个值发生了改变,另一个也会改变
比较变量时不同==
, ===
基本数据类型:
==
先进行类型转换再确定操作数的值是否相等,===
不仅比较值是否相等,还会比较数据类型是否相同引用数据类型:不管是
==
还是===
,都是比较内存地址是否相同,即比较是否都指向同一个对象
参数传递的不同(实参/形参)
函数传参都是按值传递(栈中的存储的内容):基本数据类型,拷贝的是值;复杂数据类型,拷贝的是引用地址
typeof 运算符
注意
调用
typeof null
返回的是"object"
。这是因为特殊值null
被认为是一个对空对象的引用严格来讲,函数在
ECMAScript
中被认为是对象,并不代表一种数据类型。可是, 函数也有自己特殊的属性。为此,就有必要通过typeof
操作符来区分函数和其他对象。
对一个值使用 typeof
操作符会返回下列字符串之一:
"undefined"
表示值未定义"boolean"
表示值为布尔值"string"
表示值为字符串"number"
表示值为数值"object"
表示值为对象(而不是函数)或null
"function"
表示值为函数"symbol"
表示值为符号。
number 数字类型
- 所有数字不分大小,不分整浮,不分正负,都是数字类型
- 介于 0 和 1 之间的小数,0 可以省略
科学计数法
3e8 -> 300000000
3e-4 -> 0.0003
不同进制的数字
- 二进制以 0b 开头
0b10 // 2
- 八进制以 0 开头
017 // 15
- 十六进制以 0x 开头
0xf // 15
特殊的数字型值 NaN
typeof NaN // number
- 0 除以 0 的结果是 NaN,事实上,在数学运算中,若结果不能得到数字,其结果往往都是 NaN
NaN
不自等:NaN 不等于 NaN
原因
NaN
是一种异常的结果,也就是“not a number
”,虽然它也是一个变量,但它是描述性变量,'a'不是一个数字(not a number
),'b'也不是一个数字(not a number
),但是'a'和'b'并不相等,所以NaN != NaN
也就成立了。
- 如何判断某变量值为
NaN
:isNaN()
函数可以用来判断变量值是否为NaN
,但isNaN()
也不好用,它的机理是:只要该变量传入Number()
的执行结果是NaN
,则isNaN()
函数都会得到 true
isNaN(4) //false
isNaN(NaN) //true
isNaN(undefined) // true
isNaN('我懂得') // true
string 字符串类型
数字 11 和字符串"11"在语义上是不同的,前者表达一个数量,后者是一个文本
加号可以用来拼接多个字符串
'zha' + 'ng' // 'zhang'
- 要将一个变量的值“插入”到字符串中,要“斩断链接"
var year = 2022;
var str ='zfh'+year+‘哈哈’ //zfh2022哈哈
- 空字符串,直接书写闭合的引号对即可
var str = ''
- 字符串的
length
属性
'abcd'.length //4
字符串的常用方法
charAt()
:可以得到指定位置的字符
'abcd'.charAt(1) //b
substring()
,substr()
和 slice()
方法
- substring()
substring(a,b)
可以得到从a
开始到b
结束(左闭右开,不包括b
处)的子串
'abcd'.substring(0, 2) //‘ab'
substring(a,b)
方法如果省略第二个参数,返回的子串会一直到字符串的结尾
'abcd'.substring(1) //‘bcd'
- substring(a,b)中,a 可以大于 b,数字顺序将自动调整为小数在前
'abcd'.substring(3, 2) //‘c'
- 如果任一参数小于 0 或为
NaN
,则被当作 0
'abcd'.substring(-1, -2) // 'ab'
- 如果任一参数大于字符串长度,则被当成字符串长度
'abcd'.substring(0, 100) // 'abcd'
- substr()
警告
尽管 String.prototype.substr(…)
没有严格被废弃 (as in "removed from the Web standards"), 但它被认作是遗留的函数并且可以的话应该避免使用。它并非JavaScript
核心语言的一部分,未来将可能会被移除掉。如果可以的话,使用 substring
() 替代它
- slice()
slice(a,b)
方法得到从a
开始到b
结束(不包括b
处)的子串,不会改变原字符串
'abcd'.slice(0, 2) //‘ab'
slice(a,b)
的参数可以是负数,表示倒数位置
'abcd'.slice(-2, -1) // 'c'
slice(a,b)
中,索引值a
对应的位置必须在索引值b
之前
toUpperCase()
,toLowerCase()
toUpperCase()
转为大写toLowerCase()
转为小写
indexOf()
index0f()
方法返回某个指定的字符串值在字符串中首次出现的位置- 如果要检索的字符串值没有出现,则该返回-1
'abcd'.indexOf('a') // 0
'abcd'.indexOf('e') // -1
includes()
includes()
方法用于判断一个字符串是否包含在另一个字符串中,根据情况返回 true
或 false
var str = 'Hello world'
var n = str.includes('world')
replace
和replaceAll
replace
方法用于在字符串中用一些字符替换另一些字符,或替换一个与正则表达式匹配的子串。
var str = '正则要好好看下,好好看下'
var n = str.replaceAll('好好看下', '好好看个p')
replaceAll
方法用于在字符串中用一些字符替换另一些字符,或替换一个与正则表达式匹配的子串,该函数会替换所有匹配到的子字符串。
var str = '正则要好好看下,好好看下'
var n = str.replaceAll('好好看下', '好好看个p')
boolean 类型
布尔型值只有两个:true
和 false
,分别表示真和假
最佳实践
在实际使用过程中,为了保证变量所代表的语义,不要对一个变量显式的赋值 undefined
,当需要释放一个对象时,直接赋值为 null
即可
undefined 类型
表示"缺少值",就是此处应该有一个值,但是还没有定义
null 类型
null表示"没有对象",即该处不应该有值
数据类型转换
提示
在 JS 中类型转换只有三种情况,分别是:转换为布尔值,转换为数字,转换为字符串
强制类型转换:parseInt parseFloat toString String() Number() Boolean() ....
隐式类型转换: +22、 ( - * / % > < >= <= == !=)、!!.....

转数字
有 3 个函数可以将非数值转换为数值:Number()
、parseInt()
和 parseFloat()
。Number()
是转型函数,可用于任何数据类型。后两个函数主要用于将字符串转换为数值。对于同样的参数,这 3 个函数执行的操作也不同:
Number()
布尔值:
true
和false
将分别被转为 1 和 0数字值,直接返回
null
值,返回 0undefined
,返回NaN
如果是字符串:
如果字符串中只包含数字(包括前面带正负号的情况),则转换为十进制数
如果是空字符串,则转换为 0
如果包含有效的浮点格式,则转换为浮点数值
如果包含非数字内容,则转换为
NaN
parseInt()
最佳实践
parseInt(string, radix) 函数可解析一个字符串,并返回一个整数
当参数 radix 的值为 0,或没有设置该参数时,parseInt() 会根据 string 来判断数字的基数
当忽略参数 radix , JavaScript 默认数字的基数如下:
- 如果 string 以 "0x" 开头,parseInt() 会把 string 的其余部分解析为十六进制的整数
- 如果 string 以 0 开头,那么 ECMAScript v3 允许 parseInt() 的一个实现把其后的字符解析为八进制或十六进制的数字。
- 如果 string 以 1 ~ 9 的数字开头,parseInt() 将把它解析为十进制的整数
不传第二个参数相当于让 parseInt()
自己决定如何解析,所以为了避免出错,请始终传给它第二个参数
它的工作方式是:
如果第一个字符不是数字字符或者正负号,则返回
NaN
(用parseInt()
转换空字符串时会返回 NaN)如果遇到的第一个字符是数字字符,
parseInt()
会继续解析后面的字符,直到解析完所有字符或遇到了非数字字符
parsefloat()
也是从第一个字符开始解析,一直到字符串末尾或者遇见一个无效的浮点数字字符为止
字符串中第一个小数点是有效的,而第二个小数点就是无效的了
十六进制字符串会始终被转换成 0
只解析十进制值,所以不指定第二个参数
如果字符串包含的是一个可解析为整数的数(没有小数点或者小数点后面都是零),则返回整数值
转布尔
在条件判断(if,本质是强制调用了Boolean())时,除了 undefined
, null
,false
,NaN
,''
, 0
,-0
,其他所有值都转为 true,包括所有对象
!!
const a = 1
const b = 0
const c = -1
console.log(!!a, !!b, !!c) // true false true
Boolean()
const a = 1
const b = 0
const c = -1
console.log(Boolean(a), Boolean(b), Boolean(c)) // true false true
转字符串
toString()
该方法可以用于数值,布尔值,对象,字符串值;null,undefined 没有该方法
当数值调用该方法时,可以传入一个底数参数,表示以什么底数输出数值的字符串表示
String()
String()转型函数遵循以下规则:
- 如果值有 toString()方法,则调用该方法(不传参数)并返回结果
- 如果值是 null,返回“null”
- 如果值是 undefined,返回“undefined”
用 ➕ 操作符加上一个空字符串“”
const num = 11233333333123
const str = '1'
const obj = { a: 1 }
const bool = true
console.log(typeof (num + '')) // string
console.log(typeof (obj + '')) // string
console.log(typeof (obj + '')) // string
console.log(typeof (bool + '')) // string
console.log(typeof +str) // number
对象转原始类型
对象在转换类型的时候,会调用内置的 Symbol.ToPrimitive
函数,对于该函数来说,算法逻辑一般来说如下:
- 如果已经是原始类型了,那就不需要转换了
- 调用 x.valueOf(),如果转换为基础类型,就返回转换的值
- 调用 x.toString(),如果转换为基础类型,就返回转换的值
- 如果都没有返回原始类型,就会报错
当然你也可以重写 Symbol.toPrimitive
,该方法在转原始类型时调用优先级最高。
let a = {
valueOf() {
return 0
},
toString() {
return '1'
},
[Symbol.toPrimitive]() {
return 2
},
}
1 + a // => 3