js的变量和数据类型

40 阅读9分钟

变量

var关键字

需要定义一个变量时,可以使用var关键字。

  1. var声明作用域

    使用var定义的变量会成为包含它的函数的局部变量,当函数退出时被摧毁(不存在闭包时)。

    在全局作用域中声明的变量,会成为window上的属性。

    注意:在函数函数内使用未声明变量的时,会创建一个全局变量。虽然可以通过省略var操作符定义全局变量,但不推荐这么做。在局部作用域定义全局变量很难维护,会照成内存泄漏

    function test(){
        message = "atuotuo"
    }
    test();
    console.log(message);//atuotuo
    
  2. var声明提升

    使用var声明的变量,在函数执行之前会将声明自定提升到函数作用域顶部,然后赋值未undefined。

    function test(){
    	console.log(a);
    	var a = 1;
    }
    test(); // undefined
    

let关键字

let是ES6之后提出。

let的作用和var差不多,但是最大的区别是:let的作用域是块级作用域,var的作用的是函数作用域。

if(true){
	var a = 1;
}
console.log(a) // 1


if(true){
	let b = 1;
}
console.log(b) // 报错
  1. 暂时性死区

    let和var的另一个区别是,let声明的变量没有变量提升。

    当在let声明的变量之前访问变量,会报错,在let声明之前不能使用该变量,这就是暂时性死区。

  2. 全局声明

    使用let在全局作用域中声明的变量不会成为window对象的属性。

    let声明仍然实在全局作用域中发生的,相应的变量会在页面的声明周期内存续。

  3. for循环中使用let

    在let出现之前,for循环定义的迭代变量会渗透到循环体外部。

    for(var i = 0;i < 5;i ++){
    
    }
    console.log(i); // 5
    

    但是使用let之后,这个问题就消失了,因为迭代变量的作用域仅限于for循环块内部。

    for(let i = 0;i < 5;i ++){
    
    }
    console.log(i); // referenceError:i没有定义
    

const关键字

const关键字于let基本相同,唯一一个重要的区别是它声明变量是必须初始化,且直接修改const声明的变量会导致运行错误。

const a = {name:'atuotuo'};
a = {"name":'zs'};// TypeError 给常量赋值错误

但是可以修改const声明的对象内部的属性。

const a = {name:'atuotuo'};
a.name = 'zs';// TypeError 给常量赋值错误

常见面试题

var、let 、const的区别

  1. 块级作用域:var是函数作用域,let和const是块级作用域。
  2. 变量提升:var存在变量提升,let和const没有。
  3. 全局变量添加属性:在全局作用域使用var声明变量,会添加到window属性中,而let和const不会。
  4. 暂时性死区:let和const有暂时性死区,在定义之前访问变量会报错。var没有。
  5. 初始值设置:const必须设置初始值,并且不能直接修改const定义的变量。

const变量可以修改嘛

const保证的是并不是变量的值不能改动,而是变量指向的那个内存地址不能改动。

如果const定义的基本数据不可改动,但是如果定义的为对象,可以更改对象内部的值。不可以直接更改对象(指向一个新对象)

使用定时器每隔一秒输出1,2,3,4,5

for(var i = 1;i <= 5;i++){
	setTimeout(()={
		console.log(i);
	},i * 1000)
}
// 6,6,6,6,6
for(let i = 1;i <= 5;i++){
	setTimeout(()={
		console.log(i);
	},i * 1000)
}
// 1,2,3,4,5

为什么会发生这种情况?

  1. 使用var关键字定义的变量为局部变量,因为内部使用的是定时函数,只有迭代完成之后才会执行。当退出循环时,迭代变量保存的就是退出循环的值。在之后执行定时器内的函数时,使用的i都是一个变量。
  2. 使用let定义的变量为块级作用域,js在后台为每个迭代循环声明一个新的迭代变量。所以每个setTimeout引用的都是不同的变量实例。

数据类型

Undefined

Undefined类型只有一个值,是一个特殊值undefined。

使用var或let声明了变量但没有初始化时,相当于给变量赋予了undefined。

注意:访问已经声明的变量,但是没有赋值时,为undefined。但是访问未声明的变量,会报未定义错误。

Null

Null类型只有一个值,是一个特殊值null。

从逻辑上来讲,null值表示一个空对象指针,在定义将来要保存对象值的变量时,建议使用null来初始化。

Boolean

布尔值,有两个字面量值:true和false;

将其他值转为Boolean,可以调用Boolean转型函数。

转换规则如下:

数据类型转换为true的值转换为false的值
Undefined不存在undefined
Null不存在null
String非空字符串空字符串
Number非00,NaN
Object任意对象null

Number

Number表示整数型和浮点数。

  1. 数值范围

    获取js的最小数值:Number.MIN_VALUE

    获取js的最大数值:Number.MAX_VALUE.

    当数值超过js的最大表示范围,会自动转化为Infinity(无穷值)。

    任何无法表示的负数为-Infinity,无法表示的正数为Infinity.

  2. NaN

    NaN是一个特殊的数值,表示本来要返回的数值的操作失败了。

    NaN不等于包括NaN在内的任何值。因此js提供了isNaN()函数.

    注意:使用isNaN判断时,会先将其他类型转换为数值类型,再进行判断。

  3. 数值转换

    Number():转型函数,可用于任何数据类型。

    数据类型转换后的值
    布尔值true:1,false:0
    UndefinedNaN
    null0
    字符串空字符串:0,数字字符串返回数字自动忽略前导零,非数字字符串转为NaN
    Object调用valueOf方法,返回值,如果返回结果为NaN,继续调用toString方法,再按照转换字符串的方法

    parseInt():将字符串转为整数。

    转换规则:字符串最前面的空格会被忽略,从用第一个非空字符串开始如果不是数字,加号或者减号,parseInt()立即返回NaN,这意味着空字符串也会返回NaN。(Number("") 返回0)如果第一个成功,继续依次检测每个字符,直到字符串结尾,或者碰到非数字字符。

    parseFloat():将字符串转为浮点数。

String

  1. 转换为字符串

    使用String()转型函数,或者使用.toString()方法。

  2. 字符串插值

    let name = 'atuotuo'
    let a = `My name is ${name}`;
    

Symbol

Symbol是ES6新增的数据类型,符号是原始值,且符号实例时唯一的。创建后独一无二,它主要为了解决可能出现的全局变量冲突问题。

BigInt

BigInt时一直数字类型的数据,它可以表示任何精度格式的整数,使用BigInt可以安全存储和操作大整数,即使这个数已经超出了Number能够表示的安全整数范围。

Object

Object:对象其实就是一组数据和功能的集合,对象通过new操作符后跟对象类型的名称来创建。具体使用方法后面介绍。

常见面试题

数据类型检测的方式有哪些

  1. typeof

    使用typeof可以返回任意变量的数据类型;但是数组、对象、null都会返回Object。所以判断一个变量是否为对象时不能直接用typeof obj

  2. instanceof

    可以正确判断对象的类型,其内部运行机制是判断在原型链上是否能否找到该类型的原型。

    例如:function instanceof Object // true

  3. constructor

    constructor 有两个作用,一时判断数据的类型,二是对象实例通过constructor对象访问他的构造函数。如果创建一个对象来改变他的原型,constructor就不能改变判断数据类型了。

  4. Object.prototype.toString.call()

    使用Object对象的原型方法toString()来判断数据类型。

    为什么不可以直接调用toString()进行判断?

    tostring是Object的原型方法,而Array,Function等类型做为Object的实例,都重写了tostring方法。通过call方法直接调用原型对象上的方法。

判断数组的方式有哪些?

  1. Array.isArray(arr) // 通过数据类对象自带的isArray方法
  2. **Object.prototype.toString.call(obj).slice(8,-1) === "array" ** // 通过调用string对象的原始tostring方法
  3. obj instanceof Array
  4. Array.prototype.isPrototypeOf(obj);

null 和 undefined的区别

null 和 undefined都是基本数类型,这两个数据类型分别都只有一个值,null和undefined

undefined代表的含义是未定义,null代表的含义是空对象

  • 一般变量声明了但还未定义的时候会返回undefined;void 0 的返回值undefined
  • null主要用于赋值给一些可能会返回对象的变量做为初始化,也可用于释放对象

instanceof操作符的实现原理即实现

原理:用于判断构造函数的原型对象prototype属性是否出现在对象的原型链(__proto__)中的任何位置

function myInstanceof(obj,Fun)
{
    // 获取对象的隐式原型
    let proto = Object.getPrototypeOf(obj) //相当于 obj.__proto__;
    // 获取构造函数的prototype对象
    let prototype = Fun.prototype;  
    // 判断构造函数的prototype对象是否在对象的原型链上
    while(proto)
    {
        if(proto === prototype) return true;
        proto = Object.getPrototypeOf(proto);
    }
    return false;
}

为什么0.1 + 0.2 !== 0.3 如何让其相等

计算机是通过二进制的方式存储数据的,所以在计算机计算0.1+0.2的时候,实际上计算的是两个数的二进制之和。0.1和0.2的二进制都是无限循环的数,那么计算出来的值也是无线循环的数。

双精度数的存储方式(共64位)

  • 第一部分:用于存储符号为,用于区分征服,0表示正,占1位
  • 第二部分:用于存储指数,占11位
  • 第三部分:用于存储小数,占52位

如何判断0.1 + 0.2 == 0.3?

设置误差范围,对于javascript这个值通常为2^(-52) ,在ES6中,提供了Number.EPSILON 属性,判断0.1 + 0.2 -0.3 < Number,ESPILON

isNaN 和 Number.isNaN函数的区别

  • isNaN接受参数后,将这个参数转为数值首先进行数据转换),不能转为数值的值会返回true,因此非数字传入也会返回true,会影响NaN的判断
  • Number.isNaN 会首先判断传入参数是否为数字首先判断数据类型),如果是数字在继续判断是否为NaN,不会进行数据转换,这种方法判断NaN更加准确

Object.is 、 == 和 === 的区别什么

使用双等号(==),如果两边类型不一致,则会进行强制类型转换在进行比较

使用三等号(===),首先进行类型比较,如果类型不一致,不进行类型转换,直接返回false。

object.is() 一般情况下和三等号判断的相同,他处理了一些特殊情况,如果-0和+0不相等,NaN和NaN相等。

javascript是如何进行隐式类型转换

首先介绍toPrimitive方法,这是javascript中每个值隐含的自带的方法,用于将对象 转换为 基本数据类型;

  1. 如果值为基本数据类型,则直接返回值本身

  2. 如果值为对象,则执行下面的规则:

    isPrimitive(obj,type) // 将obj将隐式转为type类型
    

    type的值为 number 或者 string

    1. 当type为number时,规则如下:

      obj调用valueOf(),如果时原始值,直接返回

      否则下一步,obj调用toString(),

    2. 当type为string时,规则如下:

      obj调用toString(),如果时原始值,直接返回

      否则下一步,obj调用valueOf()

​ 主要区别在于调用tostring和valueof的顺序不同。默认情况下:

​ 如果对象为Date对象,则type默认为String

​ 其他情况下:默认type为Nmber

什么时候会发生隐式类型转换

隐式类型转换主要发送在运算符之间,运算符只能基本类型值,所以在进行这些运算前的第一步就是将两边的值用toPrimitive 转换成基本类型值,再进行操作。

如何判断一个对象为空对象

使用JSON中的stringify方法

if(JSON.stringify(obj) == '{}'){}

使用ES新增的方法Object.keys()来判断

if(Object.keys(obj).length < 0){} // 空对象

js的数据存储

js的数据存储分为:栈内存和堆内存。

  • 栈区内存由编译器自动分配释放,存放函数的参数值,局部变量的值等。(存放值类型的值,存放引用类型指向堆区的地址)

  • 堆区内存一般由程序员分配释放,若程序员不释放,程序结束时可能由垃圾回收机制回收(存放引用类型的值)。

js数据类型分为:值类型和引用类型。

  • 值据类型:直接存储在栈中的简单数据段,占据空间小、大小固定,数据被频繁使用的数据,所以存放在栈中。
  • 引用数据类型:存储在堆中的对象,占据空间大、大小不固定。如果存储在栈中,将会影响程序运行的性能;引用数据类型在栈中存储了指针,该指针指向队中该实体的起始地址,当解释器寻找引用值时,会首先检索栈中的地址,然后根据地址从堆中获取实体值。