变量
var关键字
需要定义一个变量时,可以使用var关键字。
-
var声明作用域
使用var定义的变量会成为包含它的函数的局部变量,当函数退出时被摧毁(不存在闭包时)。
在全局作用域中声明的变量,会成为window上的属性。
注意:在函数函数内使用未声明变量的时,会创建一个全局变量。虽然可以通过省略var操作符定义全局变量,但不推荐这么做。在局部作用域定义全局变量很难维护,会照成内存泄漏
function test(){ message = "atuotuo" } test(); console.log(message);//atuotuo
-
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) // 报错
-
暂时性死区
let和var的另一个区别是,let声明的变量没有变量提升。
当在let声明的变量之前访问变量,会报错,在let声明之前不能使用该变量,这就是暂时性死区。
-
全局声明
使用let在全局作用域中声明的变量不会成为window对象的属性。
let声明仍然实在全局作用域中发生的,相应的变量会在页面的声明周期内存续。
-
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的区别
- 块级作用域:var是函数作用域,let和const是块级作用域。
- 变量提升:var存在变量提升,let和const没有。
- 全局变量添加属性:在全局作用域使用var声明变量,会添加到window属性中,而let和const不会。
- 暂时性死区:let和const有暂时性死区,在定义之前访问变量会报错。var没有。
- 初始值设置: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
为什么会发生这种情况?
- 使用var关键字定义的变量为局部变量,因为内部使用的是定时函数,只有迭代完成之后才会执行。当退出循环时,迭代变量保存的就是退出循环的值。在之后执行定时器内的函数时,使用的i都是一个变量。
- 使用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 | 非0 | 0,NaN |
Object | 任意对象 | null |
Number
Number表示整数型和浮点数。
-
数值范围
获取js的最小数值:Number.MIN_VALUE
获取js的最大数值:Number.MAX_VALUE.
当数值超过js的最大表示范围,会自动转化为Infinity(无穷值)。
任何无法表示的负数为-Infinity,无法表示的正数为Infinity.
-
NaN
NaN是一个特殊的数值,表示本来要返回的数值的操作失败了。
NaN不等于包括NaN在内的任何值。因此js提供了isNaN()函数.
注意:使用isNaN判断时,会先将其他类型转换为数值类型,再进行判断。
-
数值转换
Number():转型函数,可用于任何数据类型。
数据类型 转换后的值 布尔值 true:1,false:0 Undefined NaN null 0 字符串 空字符串:0,数字字符串返回数字自动忽略前导零,非数字字符串转为NaN Object 调用valueOf方法,返回值,如果返回结果为NaN,继续调用toString方法,再按照转换字符串的方法 parseInt():将字符串转为整数。
转换规则:字符串最前面的空格会被忽略,从用第一个非空字符串开始如果不是数字,加号或者减号,parseInt()立即返回NaN,这意味着空字符串也会返回NaN。(Number("") 返回0)如果第一个成功,继续依次检测每个字符,直到字符串结尾,或者碰到非数字字符。
parseFloat():将字符串转为浮点数。
String
-
转换为字符串
使用String()转型函数,或者使用.toString()方法。
-
字符串插值
let name = 'atuotuo' let a = `My name is ${name}`;
Symbol
Symbol是ES6新增的数据类型,符号是原始值,且符号实例时唯一的。创建后独一无二,它主要为了解决可能出现的全局变量冲突问题。
BigInt
BigInt时一直数字类型的数据,它可以表示任何精度格式的整数,使用BigInt可以安全存储和操作大整数,即使这个数已经超出了Number能够表示的安全整数范围。
Object
Object:对象其实就是一组数据和功能的集合,对象通过new操作符后跟对象类型的名称来创建。具体使用方法后面介绍。
常见面试题
数据类型检测的方式有哪些
-
typeof
使用typeof可以返回任意变量的数据类型;但是数组、对象、null都会返回Object。所以判断一个变量是否为对象时不能直接用typeof obj
-
instanceof
可以正确判断对象的类型,其内部运行机制是判断在原型链上是否能否找到该类型的原型。
例如:function instanceof Object // true
-
constructor
constructor 有两个作用,一时判断数据的类型,二是对象实例通过constructor对象访问他的构造函数。如果创建一个对象来改变他的原型,constructor就不能改变判断数据类型了。
-
Object.prototype.toString.call()
使用Object对象的原型方法toString()来判断数据类型。
为什么不可以直接调用toString()进行判断?
tostring是Object的原型方法,而Array,Function等类型做为Object的实例,都重写了tostring方法。通过call方法直接调用原型对象上的方法。
判断数组的方式有哪些?
- Array.isArray(arr) // 通过数据类对象自带的isArray方法
- **Object.prototype.toString.call(obj).slice(8,-1) === "array" ** // 通过调用string对象的原始tostring方法
- obj instanceof Array
- 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中每个值隐含的自带的方法,用于将对象 转换为 基本数据类型;
-
如果值为基本数据类型,则直接返回值本身
-
如果值为对象,则执行下面的规则:
isPrimitive(obj,type) // 将obj将隐式转为type类型
type的值为 number 或者 string
-
当type为number时,规则如下:
obj调用valueOf(),如果时原始值,直接返回
否则下一步,obj调用toString(),
-
当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数据类型分为:值类型和引用类型。
- 值据类型:直接存储在栈中的简单数据段,占据空间小、大小固定,数据被频繁使用的数据,所以存放在栈中。
- 引用数据类型:存储在堆中的对象,占据空间大、大小不固定。如果存储在栈中,将会影响程序运行的性能;引用数据类型在栈中存储了指针,该指针指向队中该实体的起始地址,当解释器寻找引用值时,会首先检索栈中的地址,然后根据地址从堆中获取实体值。