【前端】你真的理解JavaScript中的变量和数据类型吗
前言
在我们学习各种编程语言时,最先接触的就是变量和数据类型,那么我们真的理记住和理解了这些最基础的知识点吗?我们是否在笔试和面试的时候,经常对一些不起眼的知识点想不起来?那么跟着这篇文章重新回顾下这些基础内容吧。
1 变量(var、let、const)
什么是变量
- 变量是用于进行保存任意值的命名占位符。
- 字母数字下划线美元符,严格区分大小写,首字符不能是数字。
1.1 var关键字
- var关键字不初始化的情况下,会自动保存一个特殊值undefined
- var关键字在函数内部进行定义变量,会成为此函数的局部变量,即作用域是函数作用域。(变量只在当前函数内部有效,退出函数时会自动销毁)
- 在函数内部定义变量时省略var关键字,变量会成为全局变量。(在严格模式下会报错,且难维护)
- var关键字声明的变量会自动提升到函数作用域顶部。(即所有变量声明都拉到函数作用域的顶部)
- var关键字可以重复声明同一个变量。(同一作用域下,最后声明的变量会覆盖之前的变量赋值)
运行代码:
// var关键字不初始化的情况下,会自动保存一个特殊值undefined
var test1;
console.log(test1);//undefined// var关键字在函数内部进行定义变量,会成为此函数的局部变量。(变量只在当前函数内部有效,退出函数时会自动销毁)
function func1(){var test2 = "yichuan";console.log(test2);//yichuan
}
func1();
console.log(test2);//ReferenceError: test2 is not defined// 在函数内部定义变量时省略var关键字,变量会成为全局变量。(在严格模式下会报错,且难维护)
function func2(){test3 = "yichuan";console.log(test3);//yichuan
}
func2()
console.log(test3);//yichuan// var关键字声明的变量提升
function func3(){console.log(test4);//undefinedvar test4 = "yichuan";
}
func3();// var关键字可以重复声明同一个变量,同一作用域下,最后声明的变量会覆盖之前的变量赋值
function func4(){var test5 = "yichuan1";var test5 = "yichuan2";var test5 = "yichuan3";console.log(test5);//yichuan3
}
func4();
1.2 let关键字
- let关键字不初始化的情况下,会自动保存一个特殊值undefined
- let关键字的作用域是块作用域,即:function(){}、if(){}等(所有包含{}的语句)
- let关键字声明的变量不存在变量提升
- let关键字不能在同一作用域下重复声明同一个变量
- let关键字在全局作用域下声明的变量不会成为window的属性,而var关键字声明的变量会出现此情况
运行代码:
// let关键字不初始化的情况下,会自动保存一个特殊值undefined
let test1;
console.log(test1);//undefined// let关键字的声明作用域属于块级作用域
function func1(){let test2 = "yichuan";console.log(test2);//yichuan
}
func1();
console.log(test2);//ReferenceError: test2 is not definedif(true){let test3 = "yichuan";console.log(test3);//yichuan
}// let关键字声明的变量不存在变量提升
function func2(){console.log(test4);//ReferenceError: Cannot access 'test4' before initializationlet test4 = "yichuan";
}
func2();// let关键字在同一作用域下,不能重复声明同一变量
function func3(){let test5 = "yichuan1";let test5 = "yichuan2";console.log(test5);//SyntaxError: Identifier 'test5' has already been declared
}
func3();// let关键字在全局作用域下声明的变量不会成为window的属性,而var关键字声明的变量会出现此情况
var test6 = "yichuan1";
console.log(window.test6);//yichuan1let test7 = "yichuan2";
console.log(window.test7);//undefined
1.3 const关键字
- const关键字声明变量必须进行初始化赋值
- const关键字声明的变量不能更改赋值,适用于指向变量的引用(即const变量引用的是对象的话,是可以更改内部属性的)
- const关键字不存在变量提升
- const关键字的作用域也是块级作用域
- const关键字在同一作用域下不能重复声明同一个变量
运行代码:
const关键字声明变量必须进行初始化赋值
const NUM;
console.log(NUM);//SyntaxError: Missing initializer in const declarationconst PI = 3.14;
console.log(PI);//3.14// const关键字声明的变量不能更改赋值
const TEST = "YICHUAN";
TEST = "ZHENSHUAI";
console.log(TEST);//TypeError: Assignment to constant variable.// const关键字可以更改对象内部的属性
const OBJ = {name:"yichuan",age:18
}console.log(OBJ);//{ name: 'yichuan', age: 18 }
OBJ.age = 20;
console.log(OBJ);//{ name: 'yichuan', age: 20 }function func1(){console.log(SUPERNUM);//ReferenceError: Cannot access 'SUPERNUM' before initializationconst SUPERNUM = 190;
}
func1();function func2(){const SUPERNUM = 190;console.log(SUPERNUM);const SUPERNUM = 200;console.log(SUPERNUM);//SyntaxError: Identifier 'SUPERNUM' has already been declared}
func2();
1.4 小结
var | let | const |
---|---|---|
存在变量提升 | 不存在变量提升 | 不存在变量提升 |
不需要初始化 | 不需要初始化 | 需要初始化赋值 |
函数作用域 | 块级作用域 | 块级作用域 |
可以重复声明 | 不可重复声明 | 不可重复声明 |
全局作用域下,变量会成为window的属性 | 全局作用域下,变量不会成为window的属性 | 全局作用域下,变量不会成为window的属性 |
2 数据类型
2.1 数据类型的划分
简单数据类型(原始数据类型)
Undefined
:只有一个值,特殊值undefined
,表示未定义Null
:只有一个值,特殊值null
,表示空值Boolean
:只有两个字面值,true
和false
,表示布尔值Number
:整数或浮点数两类数字,还有特殊值(-Infinity
、+Infinity
、NaN
),表示数值String
:表示0或多个16位Unicode
字符序列,用""或’’、``表示,表示字符串。Symbol
:一种符号实例唯一且不可改变的数据类型,表示为符号(ES6新增)
复杂数据类型(对象数据类型)
Object
:无名值对的集合,表示对象类型。Array
、Function
等都属于对象类型。
null和undefined的区别
在简单数据类型中,我们要着重注意理解null
和undefined
的区别。
null表示一个空指针对象,有值但是值为空值。所以在定义将来要保存对象值得变量时,建议使用null
来进行初始化,不要使用其他值。
undefined表示一个缺省值,即未定义值,此处本该有一个值,但是尚未定义。
String
String
数据类型表示0个或多个16位Unicode
字符序列,可以用""、’'以及反引号表示。我们可以使用length
获取字符串的字符长度,但是当字符串包含双字节字符时,那么length
返回的值可能不是准确的字符数。
为什么划分为简单数据类型和复杂数据类型?
在ES标准中存在着两种不同类型的数据:原始值和引用值。原始值──最简单的不可改变的数据,引用值──保存在内存中的对象。
不可变性和动态属性
不可变性指的是原始值本身是不可变的,动态属性指的是引用值可以动态增加、删除和修改属性和方法。分别举个栗子演示一下吧。
原始值:
let str = "yichuan";
str.substr(1,4);//"ichu"
str.slice(1,5);//"ichu"
str[5] = "zhenshuai";
console.log(str);//yichuan
我们看到,无论我们怎么操作str的字符串,都是在原字符串的基础上产生新的字符串,而非直接更改原字符串。
引用值:
let obj = {name:"yichuan",age:18
}
console.log(obj);//{name: "yichuan", age: 18}
obj.age = 20;
obj.gender = "male";
console.log(obj);//{name: "yichuan", age: 20, gender: "male"}
我们看到,对于对象obj可以随时随地进行增删改查操作属性和方法,可以进行动态改变。
对于原始值而言,其没有属性,只能使用原始字面量进行初始化。即使使用new关键字创建Object的字符串实例,其行为和方法也类似原始值。
let name = "yichuan";
let name2 = new String("yichuan2");
name.age = 18;
name2.age = 20;
console.log(name.age);//ubdefined
console.log(name2.age);//20
console.log(typeof name);//string
console.log(typeof name2);//object
2.2 值传递和引用传递
其实在内存空间根据存储形式分为栈内存和堆内存。
栈内存 | 堆内存 |
---|---|
存储的值大小固定 | 存储的值大小不定,可动态调整 |
空间较小 | 空间较大 |
可以直接操作其保存的变量,运行效率高 | 无法直接操作其内部存储,使用引用地址进行获取,运行效率低下 |
由系统自动分配的空间 | 通过代码进行分配空间 |
Javascript
就是根据值得存储位置分为简单数据类型和复杂数据类型的,也就是原始类型和引用类型。原始类型的值是存储在栈内存中的,在进行变量定义时系统自动分配内存空间;引用数据类型的引用地址存储在栈内存中,而真实的值存储在堆内存中,引用地址就是指向堆内存的。
我们看到,每定义一个变量赋值基本数据类型,都是在栈内存中开辟一块空间进行存储。而引用类型的值实际存储在堆内存中,它在栈中只存储了一个固定长度的地址,这个地址指向堆内存中的值。
所以当复制变量为基本数据类型时,复制的就是完完全全的变量和值;而复制变量为引用类型时,复制的只是值得引用地址。因此才会有深拷贝和浅拷贝一说。
2.3 数值转换
我们知道有三个函数能够将非数值类型转为数值类型,分别是:Number()
、parseInt()
以及parsetFloat()
,可适用于任何类型。字符串是不可变的,一旦创建,它的值不能变,要修改值的值必须销毁之前的字符串重新赋值。
toString
可以将当前值转换为字符串等价物,注意:null
和undefined
没有此toString
。String
和toString
的作用一样,但是它对null
和undefined
分别返回"null
“和”undefined
"。
Number函数
类型 | 值 |
---|---|
Boolean | ture转为1,false转为0 |
Number | 返回原数值即可 |
null | 0 |
undefined | NaN |
String | (1)对于字符串内的数值字符(无论什么进制、还是浮点型),会转换成对应Number类型的十进制数值。(2)对于空字符串转为0.(3)其他情况转为NaN |
对象类型 | (1)先使用valueOf()方法进行转换 (2)若转换得到的值为NaN,则调用toString()转换,而后按照String类型进行转换 |
parseInt函数
parseInt(string, radix)
解析一个字符串并返回指定基数的十进制整数, radix
是2-36
之间的整数,表示被解析字符串的基数。
要被解析的值。如果参数不是一个字符串,则将其转换为字符串(使用
ToString
抽象操作)。字符串开头的空白符将会被忽略。radix
可选 从 2 到 36,表示字符串的基数。例如指定 16 表示被解析值是十六进制数。请注意,10不是默认值!
相比于Number()
方法而言,更加专注于对字符串中的数值进行操作。parseInt()
会从非空格字符开始进行转换。
- 当第一个字符不是数值字符、加号或减号,则立即返回NaN。(空字符串也返回NaN)
- 当第一个字符是数值字符或加号减号时,则依次挨个检测转换到字符串末尾,当碰到非数值字符则会忽略,而浮点字符也会转为只剩整数部分的值。如
123blue22.22
则转换为12322
。 - 当字符串中第一个字符是数值字符,且以
0x
等进制字符开头,则会转换为对应的十进制数。
parseFloat函数 和parseInt
函数工作类似,只不过检测是浮点型罢了。
2.4 常见类型转换
你应该知道的,Javascript
是一种弱类型的语言,因此在使用过程中会出现频繁的类型转换。而类型转换也会根据是否手动转换分为隐式转换和强制转换。
强制转换相对比较简单,无非就是我们自行进行转换,那么我们来介绍下隐式转换吧。
Boolean类型的等价隐式转换
我们知道if等判断、控制语句等会优先将其他类型值自动转为布尔值进行比较。
数据类型 | 转换为true的值 | 转换为false的值 |
---|---|---|
Boolean | true | false |
String | 非空字符串 | “”(空字符串) |
Number | 非0数值(包括无穷值) | 0、NaN |
Object | 任意对象(包括[],{}等) | null |
Undefined | N/A(不存在) | undefined |
运算符转换
我们经常遇到1+"yichuan"
和1-"yichuan"
等不同类型之间进行运算的情况,一般情况(-*/
)等会直接将非Number
类型转为Number
类型进行运算。你以为1+"1"
相加会等于2,其实得到却是11
,惊不惊喜,意不意外。
而在使用+
进行运算时,就需要格外注意。当进行+
运算时,有一侧是String
,那么则会将另一侧的数据类型转换为String
进行拼接;当+
运算的一侧Number
,而另一侧是引用类型时,则会将引用类型和Number
都转成String
类型后进行拼接;只有在+
一侧是Number
,另一侧非String
和引用类型时,才会将另一侧的类型转换为Number
后进行数值运算。
运算 | 值 |
---|---|
1 - true | 0 |
1 - null | 1 |
1 - “1” | 0 |
1 - []、1 - {} | 0 |
1 * undefined | NaN |
2 * [“1”] | 2 |
1 + “1” | 11 |
1 + null | 1 |
123 + {} | 123[object object] |
和=判断
===
和==
的区别,在于==
比较的两侧的值,当两侧的类型不同时会发生隐式转换;而===
比较的是两侧值和类型,当两者有一不同就得到false
,即不会发生隐式转换。对于===
就不探讨了,我们探讨下==
。
NaN涉及到NaN
的各种操作返回都是NaN
,NaN
也不等于任何值。也就是NaN == NaN
返回的也是false
。
Boolean和任何类型进行比较,都会优先将Boolean
转为Number
,但是在与undefined
和null
进行比较时,要考虑到两者均为假值的情况。因为undefined
和null
虽然会转为false
,但是false
是Boolean
类型会转为数值0所以不等。
比较 | 值 |
---|---|
true == 1 | true |
true == “2” | false |
true == [“1”] | true |
true == [“2”] | true |
true == [] | false |
undefined == false | false |
null == false | false |
String和Number在进行两者比较的时候,String
类型也会优先转换为Number
类型进行比较。
1 == "1" //true
0 == "" //true
null和undefined两者是假值,所以在比较的时候都会优先转换为false
进行比较。
比较 | 值 |
---|---|
null == undefined | true |
null == “” | false |
null == 0 | false |
null == false | false |
undefined == “” | false |
undefined == 0 | false |
undefines == false | false |
表中的转换前面都有介绍,null
和undefined
都会转换成false
进行比较,而其他则对应转换为numder
类型进行比较。
原始类型和引用类型
在两者进行比较时,有个需要注意的是引用类型会转换成原始类型进行比较,其实就是先转换成String
或Boolean
再进行比较。
`[object object]` == {} //true
`1,2,3` == [1,2,3] //true
而有个比较困扰的问题就是[] == ![]
的比较得到的为什么是true
?
[] == ![]
//1.首先要知道!的优先级高于==,因此![]先转换为false
//2.而![]转换为false后,会将false转换为数值0
//3.左侧的[]在==运算时,直接会转为数值0
//4.==两侧均为0,所以得到true
2.5 数据类型的判断
typeof判断
typeof
是Javascript原生内置的类型判断运算符,但是由于具有一定得局限性,在进行一些类型判断时并不能清晰准确。
示例:
function func(){console.log("yichuan");
};
let obj = {name: 'yichuan'
};
console.log(typeof undefined);//undefined
console.log(typeof true);//boolean
console.log(typeof 100);//number
console.log(typeof "yichuan");//string
console.log(typeof Symbol(1));//symbolconsole.log(typeof null);//object
console.log(typeof [1,2,3]);//object
console.log(typeof func);//function
console.log(typeof obj);//object
console.log(typeof Math);//object
console.log(typeof new Date());//object
console.log(typeof /abc/ig);//object
console.log(typeof new Error("error"));//object
我们可以看到,typeof
可以判断所有基本数据类型,能够准确判断(number
、boolean
、string
、symbol
、undefined
),但是对于null
和object
类型(object
、function
、Error
等)却无能为力。
注意
typeof null
之所以返回值为object
,是因为在JS的最初版本使用的是32位系统,出于性能考虑使用了低位存储了变量的类型信息。000打头的表示对象,而null
也表示为全0,这就造成了误判其为object
类型,为了维持稳定也就一直持续到现在,所以这是js语言设计的一个缺陷。
instanceof判断
instanceof
操作符可以用于判断引用类型具体是什么类型的对象,通过内部机制的原型链查找去寻找对象的prototype
。但是其不能用于检测某些简单数据类型,因为它们没有原型怎么查找,所以也是有所缺陷的。
console.log([1,2,3] instanceof Array);//true
console.log([1,2,3] instanceof Object);//truefunction func(){console.log("yichuan");
}
console.log(func instanceof Function);//true
console.log(func instanceof Object);//trueconsole.log("yichuan" instanceof String);//false
console.log(new String("yichuan") instanceof String);//true
我们可以看到func和[1,2,3]使用instanceof
也都会指向Object
类型,其实instanceof
检测对象数据类型,也并不是很准确,同样会造成判断困扰。之所以会这样,这和原型链的查找机制相关,这里进行简单介绍,后面将单独写一篇文章进行介绍。(先挖一个坑)
原型链的几条基本准则:
- 所有复杂数据类型都有对象特性,可以进行自由进行属性操作
- 所有对象沿着原型链查找,查到顶部都是
null
空对象 - 所有复杂数据类型都具有一个
__proto__
(隐式原型)属性和prototype
(显式原型)属性,都是一个普通对象,其__proto__
值指向构造函数的protoype
- 当查找对象的属性时,会先对对象本身属性进行比较,如果对象本身没有此属性,则沿着原型链查找它的
__proto__
值
Object.prototype.toString.call()判断
我们看到前面的typeof
和instanceof
用于判断数据类型都具有局限性和缺陷,那么为了实现准确判断数据类型应该采用什么方法呢?答案是:Object.prototype.toString.call()
。
每一个对象数据类型都有toString
方法,也就是说toString()
方法会被每个Object
对象继承。如果此方法在自定义对象中未被覆盖,则toString()
返回 "[object type]
"的type
就是表示对象的类型。
我们看到自定义对象中未被覆盖被着重强调,意思就是告诉我们大部分对象数据类型重新书写了toString
方法,比如:Array
、Date
等。但是,实际生产中Object.prototype.toString
可以用于解决我们遇到的大部分类型判断问题,因此你可以放心使用。在使用时,我们还需要添加call
改变this
的指向。
Object.prototype.toString
原理是调用时取值内部的 [[Class]]
属性值,拼接成 '[object ' + [[Class]] + ']'
这样的字符串并返回. 然后我们使用 call
方法来获取任何值的数据类型.
方法调用 | 判断结果 |
---|---|
Object.prototype.toString.call(undefined)
|
[object Undefined]
|
Object.prototype.toString.call(true)
|
[object Boolean]
|
Object.prototype.toString.call(null)
|
[object Null]
|
Object.prototype.toString.call("yichuan")
|
[object String]
|
Object.prototype.toString.call(100)
|
[object Number]
|
Object.prototype.toString.call(Symbol(100))
|
[object Symbol]
|
Object.prototype.toString.call({})
|
[object Object]
|
Object.prototype.toString.call([])
|
[object Array]
|
Object.prototype.toString.call(new Error())
|
[object Error]
|
Object.prototype.toString.call(new Date())
|
[object Date]
|
Object.prototype.toString.call(function(){})
|
[object Function]
|
Object.prototype.toString.call(/123/gi)
|
[object RegExp]
|
Object.prototype.toString.call(Math)
|
[object Math]
|
Object.prototype.toString.call(JSON)
|
[object JSON]
|
Object.prototype.toString.call(window)
|
[object Window]
|
集大成者──JQuery中的类型判断
JQuery
的类型判断源码,其实就是使用typeof
判断简单数据类型,使用Object.prototype.toString.call()
来判断复杂数据类型,使用class2type
截取取到数据类型的名称。快看这,这是源码哦,写的很简单,并没有大家想象的那么复杂。
var class2type = {};
jQuery.each( "Boolean Number String Function Array Date RegExp Object Error Symbol".split( " " ),
function( i, name ) {class2type[ "[object " + name + "]" ] = name.toLowerCase();
} );type: function( obj ) {if ( obj == null ) {return obj + "";}return typeof obj === "object" || typeof obj === "function" ?class2type[Object.prototype.toString.call(obj) ] || "object" :typeof obj;
}isFunction: function( obj ) {return jQuery.type(obj) === "function";
}
参考文章
《JavaScript高级程序设计(第4版)》
《一看就懂的var、let、const三者区别》
《你真的掌握变量和类型了吗》
《Javascript 中的数据类型判断》
写在最后
感谢大家的阅读,我将继续和大家分享更多优秀的文章,此文参考了大量书籍和文章,如果有错误和纰漏,希望能给予指正。
关注微信公众号【前端万有引力】,及时获取更多相关技术、面试经验等文章。
【前端】你真的理解JavaScript中的变量和数据类型吗相关推荐
- 【Python】Python实战从入门到精通之一 -- 教你深入理解Python中的变量和数据类型
本文是Python实战–从入门到精通系列的第一篇文章: Python实战从入门到精通之一 – 教你深入理解Python中的变量和数据类型 文章目录 1.变量 1.1 变量命名规则 1.2 变量名称错误 ...
- javascript基础系列:javascript中的变量和数据类型(一)
javascript基础系列:javascript中的变量和数据类型(一) 今天开始去重新系统温习一遍js基础,并作下记录 javascript是由三部分组成: ECMASCRIPT(ES): 描述了 ...
- 如何理解JavaScript中给变量赋值,是引用还是复制
一.JavaScript中值的类型 JavaScript中的值分为2大类:基本类型和引用类型.每种类型下面又分为5种类型. 基本类型: 数字类型:Number:字符串类型:String:布尔类型:Bo ...
- 理解javascript中的回调函数(callback)【转】
在JavaScrip中,function是内置的类对象,也就是说它是一种类型的对象,可以和其它String.Array.Number.Object类的对象一样用于内置对象的管理.因为function实 ...
- javascript 符号_理解JavaScript中“ =”符号的直观指南
javascript 符号 by Kevin Kononenko 凯文·科诺年科(Kevin Kononenko) 理解JavaScript中" ="符号的直观指南 (A Visu ...
- 深入理解JavaScript中的this关键字
在JavaScript中this变量是一个令人难以摸清的关键字,this可谓是非常强大,充分了解this的相关知识有助于我们在编写面向对象的JavaScript程序时能够游刃有余. 对于this变量最 ...
- [转载] Java内存管理-你真的理解Java中的数据类型吗(十)
参考链接: Java中的字符串类String 1 做一个积极的人 编码.改bug.提升自己 我有一个乐园,面向编程,春暖花开! 推荐阅读 第一季 0.Java的线程安全.单例模式.JVM内存结构等知识 ...
- 理解javascript中的回调函数(callback)
理解javascript中的回调函数(callback) 在JavaScrip中,function是内置的类对象,也就是说它是一种类型的对象,可以和其它String.Array.Number.Obje ...
- 理解JavaScript中部分设计模式
理解JavaScript中部分设计模式 什么是设计模式 在软件工程中,设计模式是软件设计中常见问题可重用的方案.设计模式代表着经验丰富的软件开发人员使用的最佳实践.设计模式可以被认为是编程模板. 为什 ...
- 帮助你更好理解javascript中easing功能的网站 - Easings.net
日期:2012-10-17 来源:GBin1.com 如果你开发过jQuery的动画效果的话,肯定接触过一个jQuery插件:jquery.easing plugin,这个插件可以帮助你生成不同类型 ...
最新文章
- python array 语法_Python基本语法
- 【 Vivado 】时钟组(Clock Groups)
- 如何做相册_手机里的照片太多,不得已只能删除?那就试试制作电子相册吧
- Myeclipse修改设置Default VM Arguments
- 'webpack-dev-server' 不是内部或外部命令,也不是可运行的程序
- How to check number of Active connections in SQL server?
- Ubuntu 11.04 安装后要做的20件事
- 【车间调度】基于matlab GUI遗传算法求解车间调度问题【含Matlab源码 049期】
- 祭祀php,个性的qq网名_唱首祭歌,祭祀你的离去。
- [摩斯密码表]摩斯密码对照表
- vivo怎么设置默认桌面_vivoz3怎样设置默认桌面?
- 深入理解HashMap底层数据结构
- vue文件下载及重命名
- 上海航芯|物联网安全芯片ACL16简介
- python调用报表制作工具_Python如何使用xlwt制作一个表格
- c轴 t轴 l轴_数控加工中心3轴、3+2轴、5轴加工的区别是什么?
- 前后端分离时ajax发送请求时后端能接送,但是前端的response为空时
- js 中try catch用法
- UTF-8 带BOM 和 UTF-8无BOM 的区别?
- 文件cpy改进,文件加密,对文件两次运算可解密,密码65