跳至主要內容

变量与数据类型

Mr.Chen前端基石JS基础变量数据类型大约 15 分钟约 4434 字

变量

变量的默认值

  1. 一个变量只定义,但没有赋初值,默认值是 undefined

  2. 一个变量只有被 var 定义,并赋初值之后,才算正式初始化完成

变量的常见错误

::: warning 注意

虽然可以通过省略 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 属性是否可以被删除,布尔值
  • 属性 ab 的描述对象区别在于是否可删除, 这个特性属性 aconfigurable: false 不可删除,属性 bconfigurable: 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

参考:Object.defineProperty()open in new window

  • 尝试使用一个既没有被var定义过,也没有赋过值的字符就会产生引用错误。
console.log(b) //  b is not defined

变量的合法命名

::: tip 提示

变量,函数,对象的属性 一般都采用小驼峰命名法,类名,构造函数采用大驼峰命名法,即首字母也要大写

关键字、保留字、truefalsenull ,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

::: tip 看道题 下列代码输出结果是?

var val = 12
function fun1() {
  console.log(val)
  var val = 20
  console.log(val)
}
fun1()
// undefined
// 20

:::

JS 基本数据类型

深入理解 js 数据类型与堆栈内存open in new window

数据类型分类

  • 基本数据类型:string,boolean,number,symbol(ES6 新增),undefined, null
  • 引用数据类型:object
  • js 的常见内置对象:Date,Array,Math,Number,Boolean,String,Array,RegExp,Function...

::: tip 提示 其中 SymbolES6 中新增的数据类型:

Symbol符号是原始值,且符号实例是唯一、不可变的。 符号的用途是确保对象属性使用唯一标识符,不会发生属性冲突的危险。

更多 Symbol 细节见 ES6 章节

:::

基本数据类型和引用数据类型的区别

内存的分配不同

  • 基本数据类型存储在栈中
  • 复杂数据类型存储在堆中,栈中存储的变量,是指向堆中的引用地址

访问机制不同

  • 基本数据类型是按值访问
  • 复杂数据类型按引用访问,JS 不允许直接访问保存在堆内存中的对象,在访问一个对象时,首先得到的是这个对象在堆内存中的地址,然后再按照这个地址去获得这个对象的值

复制变量时不同a=b

  • 基本数据类型:a=b;是将 b 中保存的原始值的副本赋值给新变量 a,ab 完全独立,互不影响

  • 复杂数据类型:a=b;将 b 保存的对象内存的引用地址赋值给了新变量 a;a b 指向了同一个堆内存地址,其中一个值发生了改变,另一个也会改变

比较变量时不同== , ===

  • 基本数据类型:==先进行类型转换再确定操作数的值是否相等===不仅比较值是否相等,还会比较数据类型是否相同

  • 引用数据类型:不管是 == 还是 === ,都是比较内存地址是否相同,即比较是否都指向同一个对象

参数传递的不同(实参/形参)

函数传参都是按值传递(栈中的存储的内容):基本数据类型,拷贝的是值;复杂数据类型,拷贝的是引用地址

typeof 运算符

::: warning 注意

  • 调用 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 不等于 NaNopen in new window

::: tip 原因 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()
  1. substring(a,b)可以得到从 a 开始到 b 结束(左闭右开,不包括 b)的子串
'abcd'.substring(0, 2) //‘ab'
  1. substring(a,b)方法如果省略第二个参数,返回的子串会一直到字符串的结尾
'abcd'.substring(1) //‘bcd'
  1. substring(a,b)中,a 可以大于 b,数字顺序将自动调整为小数在前
'abcd'.substring(3, 2) //‘c'
  1. 如果任一参数小于 0 或为 NaN,则被当作 0
'abcd'.substring(-1, -2) // 'ab'
  1. 如果任一参数大于字符串长度,则被当成字符串长度
'abcd'.substring(0, 100) // 'abcd'
  • substr()

::: warning 警告 尽管 String.prototype.substr(…) 没有严格被废弃 (as in "removed from the Web standards"), 但它被认作是遗留的函数并且可以的话应该避免使用。它并非JavaScript核心语言的一部分,未来将可能会被移除掉。如果可以的话,使用 substring() 替代它 :::

  • slice()
  1. slice(a,b)方法得到从a开始到 b 结束(不包括 b 处)的子串,不会改变原字符串
'abcd'.slice(0, 2) //‘ab'
  1. slice(a,b)的参数可以是负数,表示倒数位置
'abcd'.slice(-2, -1) // 'c'
  1. slice(a,b)中,索引值 a 对应的位置必须在索引值 b 之前

toUpperCase(),toLowerCase()

  • toUpperCase() 转为大写
  • toLowerCase() 转为小写

indexOf()

  • index0f()方法返回某个指定的字符串值在字符串中首次出现的位置
  • 如果要检索的字符串值没有出现,则该返回-1
'abcd'.indexOf('a') // 0
'abcd'.indexOf('e') // -1

includes()

includes() 方法用于判断一个字符串是否包含在另一个字符串中,根据情况返回 truefalse

var str = 'Hello world'
var n = str.includes('world')

replacereplaceAll

  • replace方法用于在字符串中用一些字符替换另一些字符,或替换一个与正则表达式匹配的子串。
var str = '正则要好好看下,好好看下'
var n = str.replaceAll('好好看下', '好好看个p')
  • replaceAll方法用于在字符串中用一些字符替换另一些字符,或替换一个与正则表达式匹配的子串,该函数会替换所有匹配到的子字符串。
var str = '正则要好好看下,好好看下'
var n = str.replaceAll('好好看下', '好好看个p')

boolean 类型

布尔型值只有两个:true false,分别表示真和假

undefined 类型

undefined 表示一个变量自然的、最原始的状态值,就是此处应该有一个值,但是还没有定义

undefined 又是值,又是一种类型,这种类型只有它自己一个值

typeof undefined // undefined

undefined一般会在以下 4 种场景中出现:

  • 声明了一个变量,但没有赋值,就等于 undefined
var a
console.log(a) // undefined
  • 函数定义了形参,但没有传递实参,该参数等于 undefined
//函数定义了形参 a
function f(a) {
  console.log(a) // undefined
}
f() //未传递实参
  • 访问对象上不存在的属性,该属性的值为 undefined
var a = new Object()
a.p // undefined
  • 函数没有返回值时,默认返回 undefined
var a = f()
a // undefined

null 类型

null 表示一个变量被人为的设置为空对象,而不是原始状态,即该处不应该有值

使用 typeof 检测 null 值,结果是 object,这点 尤其要注意 ,类型和 typeof 检测结果并不总是一一对应,比如数组用 typeof 检测结果也是 object

null一般会在以下 2 种场景中出现:

  • 利用 document.getElementById(‘XXX’) 寻找一个不存在的元素,将返回 null
console.log(null == document.getElementById('notExistElement')) // true
  • 作为对象原型链的终点
Object.getPrototypeOf(Object.prototype) // null

null 和 undefined 的区别

::: tip 最佳实践 在实际使用过程中,为了保证变量所代表的语义,不要对一个变量显式的赋值 undefined,当需要释放一个对象时,直接赋值为 null 即可 :::

undefined vs nullopen in new window

  1. 数据类型不同

  2. 转换为数值区别:null 是一个表示"无"的对象,转为数值时为 0;undefined 是一个表示"无"的原始值,转为数值时为 NaN

  3. null == undefined会返回true,因为它们是类似的值;但null === undefined会返回false,因为它们是不同类型的值。

数据类型转换

::: tip 提示 在 JS 中类型转换只有三种情况,分别是:转换为布尔值,转换为数字,转换为字符串 :::

数据类型转换全面
数据类型转换全面

转数字

有 3 个函数可以将非数值转换为数值:Number()parseInt()parseFloat()Number()是转型函数,可用于任何数据类型。后两个函数主要用于将字符串转换为数值。对于同样的参数,这 3 个函数执行的操作也不同:

Number()

  1. 布尔值:true false 将分别被转为 1 和 0

  2. 数字值,直接返回

  3. null 值,返回 0

  4. undefined,返回 NaN

  5. 如果是字符串:

  • 如果字符串中只包含数字(包括前面带正负号的情况),则转换为十进制数

  • 如果是空字符串,则转换为 0

  • 如果包含有效的浮点格式,则转换为浮点数值

  • 如果包含非数字内容,则转换为 NaN

parseInt()

::: tip 最佳实践 不传底数参数相当于让 parseInt()自己决定如何解析,所以为了避免出错,请始终传给它第二个参数 :::

parseInt()可以为函数提供第二个参数:转换时使用的基数(2,8,10,16)

它的工作方式是:

  • 如果第一个字符不是数字字符或者正负号,则返回 NaN(用 parseInt()转换空字符串时会返回 NaN)

  • 如果遇到的第一个字符是数字字符,parseInt()会继续解析后面的字符,直到解析完所有字符或遇到了非数字字符

parsefloat()

也是从第一个字符开始解析,一直到字符串末尾或者遇见一个无效的浮点数字字符为止

字符串中第一个小数点是有效地,而第二个小数点就是无效的了

十六进制字符串会始终被转换成 0

只解析十进制值,所以不指定第二个参数

如果字符串包含的是一个可解析为整数的数(没有小数点或者小数点后面都是零),则返回整数值

转布尔

在条件判断时,除了 undefinednullfalseNaN''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
上次编辑于: