前言

在我们学习各种编程语言时,最先接触的就是变量和数据类型,那么我们真的理记住和理解了这些最基础的知识点吗?我们是否在笔试和面试的时候,经常对一些不起眼的知识点想不起来?那么跟着这篇文章重新回顾下这些基础内容吧。

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:只有两个字面值,truefalse,表示布尔值
  • Number:整数或浮点数两类数字,还有特殊值(-Infinity+InfinityNaN),表示数值
  • String:表示0或多个16位Unicode字符序列,用""或’’、``表示,表示字符串。
  • Symbol:一种符号实例唯一且不可改变的数据类型,表示为符号(ES6新增)

复杂数据类型(对象数据类型)

  • Object:无名值对的集合,表示对象类型。ArrayFunction等都属于对象类型。

null和undefined的区别

在简单数据类型中,我们要着重注意理解nullundefined的区别。

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可以将当前值转换为字符串等价物,注意:nullundefined没有此toString
  • StringtoString的作用一样,但是它对nullundefined分别返回"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) 解析一个字符串并返回指定基数的十进制整数, radix2-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的各种操作返回都是NaNNaN也不等于任何值。也就是NaN == NaN返回的也是false

Boolean和任何类型进行比较,都会优先将Boolean转为Number,但是在与undefinednull进行比较时,要考虑到两者均为假值的情况。因为undefinednull虽然会转为false,但是falseBoolean类型会转为数值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

表中的转换前面都有介绍,nullundefined都会转换成false进行比较,而其他则对应转换为numder类型进行比较。

原始类型和引用类型
在两者进行比较时,有个需要注意的是引用类型会转换成原始类型进行比较,其实就是先转换成StringBoolean再进行比较。

`[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可以判断所有基本数据类型,能够准确判断(numberbooleanstringsymbolundefined),但是对于nullobject类型(objectfunctionError等)却无能为力。

注意
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()判断

我们看到前面的typeofinstanceof用于判断数据类型都具有局限性和缺陷,那么为了实现准确判断数据类型应该采用什么方法呢?答案是:Object.prototype.toString.call()

每一个对象数据类型都有toString方法,也就是说toString()方法会被每个Object对象继承。如果此方法在自定义对象中未被覆盖,则toString()返回 "[object type]"的type就是表示对象的类型。

我们看到自定义对象中未被覆盖被着重强调,意思就是告诉我们大部分对象数据类型重新书写了toString方法,比如:ArrayDate等。但是,实际生产中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中的变量和数据类型吗相关推荐

  1. 【Python】Python实战从入门到精通之一 -- 教你深入理解Python中的变量和数据类型

    本文是Python实战–从入门到精通系列的第一篇文章: Python实战从入门到精通之一 – 教你深入理解Python中的变量和数据类型 文章目录 1.变量 1.1 变量命名规则 1.2 变量名称错误 ...

  2. javascript基础系列:javascript中的变量和数据类型(一)

    javascript基础系列:javascript中的变量和数据类型(一) 今天开始去重新系统温习一遍js基础,并作下记录 javascript是由三部分组成: ECMASCRIPT(ES): 描述了 ...

  3. 如何理解JavaScript中给变量赋值,是引用还是复制

    一.JavaScript中值的类型 JavaScript中的值分为2大类:基本类型和引用类型.每种类型下面又分为5种类型. 基本类型: 数字类型:Number:字符串类型:String:布尔类型:Bo ...

  4. 理解javascript中的回调函数(callback)【转】

    在JavaScrip中,function是内置的类对象,也就是说它是一种类型的对象,可以和其它String.Array.Number.Object类的对象一样用于内置对象的管理.因为function实 ...

  5. javascript 符号_理解JavaScript中“ =”符号的直观指南

    javascript 符号 by Kevin Kononenko 凯文·科诺年科(Kevin Kononenko) 理解JavaScript中" ="符号的直观指南 (A Visu ...

  6. 深入理解JavaScript中的this关键字

    在JavaScript中this变量是一个令人难以摸清的关键字,this可谓是非常强大,充分了解this的相关知识有助于我们在编写面向对象的JavaScript程序时能够游刃有余. 对于this变量最 ...

  7. [转载] Java内存管理-你真的理解Java中的数据类型吗(十)

    参考链接: Java中的字符串类String 1 做一个积极的人 编码.改bug.提升自己 我有一个乐园,面向编程,春暖花开! 推荐阅读 第一季 0.Java的线程安全.单例模式.JVM内存结构等知识 ...

  8. 理解javascript中的回调函数(callback)

    理解javascript中的回调函数(callback) 在JavaScrip中,function是内置的类对象,也就是说它是一种类型的对象,可以和其它String.Array.Number.Obje ...

  9. 理解JavaScript中部分设计模式

    理解JavaScript中部分设计模式 什么是设计模式 在软件工程中,设计模式是软件设计中常见问题可重用的方案.设计模式代表着经验丰富的软件开发人员使用的最佳实践.设计模式可以被认为是编程模板. 为什 ...

  10. 帮助你更好理解javascript中easing功能的网站 - Easings.net

    日期:2012-10-17  来源:GBin1.com 如果你开发过jQuery的动画效果的话,肯定接触过一个jQuery插件:jquery.easing plugin,这个插件可以帮助你生成不同类型 ...

最新文章

  1. python array 语法_Python基本语法
  2. 【 Vivado 】时钟组(Clock Groups)
  3. 如何做相册_手机里的照片太多,不得已只能删除?那就试试制作电子相册吧
  4. Myeclipse修改设置Default VM Arguments
  5. 'webpack-dev-server' 不是内部或外部命令,也不是可运行的程序
  6. How to check number of Active connections in SQL server?
  7. Ubuntu 11.04 安装后要做的20件事
  8. 【车间调度】基于matlab GUI遗传算法求解车间调度问题【含Matlab源码 049期】
  9. 祭祀php,个性的qq网名_唱首祭歌,祭祀你的离去。
  10. [摩斯密码表]摩斯密码对照表
  11. vivo怎么设置默认桌面_vivoz3怎样设置默认桌面?
  12. 深入理解HashMap底层数据结构
  13. vue文件下载及重命名
  14. 上海航芯|物联网安全芯片ACL16简介
  15. python调用报表制作工具_Python如何使用xlwt制作一个表格
  16. c轴 t轴 l轴_数控加工中心3轴、3+2轴、5轴加工的区别是什么?
  17. 前后端分离时ajax发送请求时后端能接送,但是前端的response为空时
  18. js 中try catch用法
  19. UTF-8 带BOM 和 UTF-8无BOM 的区别?
  20. 文件cpy改进,文件加密,对文件两次运算可解密,密码65

热门文章

  1. chartengine 图表的应用流程
  2. html制作钟表盘,jquery+html5制作超酷的圆盘时钟表
  3. 分析Android长按电源键事件并定制长按电源dialog
  4. 【CityEngine】城市模型构建概述(持续更新)
  5. MongoDB~从入门到入坑。
  6. Java基础篇--集合(collection)
  7. Millet谷仓:区块链重构电商
  8. 世界上最伟大的十大公式
  9. Oracle EBS FSG报表输出记录追溯出错
  10. high sierra php,關於macOS High Sierra 10.13 中,XAMPP php7 安裝memcache的問題