[toc]
github地址
js的变量和数据类型
js的数据类型分为:原始类型(即基本类型)和对象类型(即引用数据类型)
- 基本类型:
Undefined
、Null
、Number
、Boolean
、String
、Symbol
- 引用数据类型:
Object
、Array
、Function
、Date
、RegExp
等等
一、基本类型和引用数据类型的区别
基本类型
-
基本类型不能由属性
let name = 'Tom'; name.age = 18; console.log(name.age) // undefined
// 只有引用值可以动态添加后面的属性 let name = new Sttring('Tom') // name -> String {"Tom"} name.age = 18 console.log(name.age) // 18 // name -> String {"Tom", age: 19} typeof name // "object"
-
基本类型的值是不变的
let name = 'tom' name.toUpperCase() // "TOM" name // tom
-
基本类型是按值传递
let name = 'tom'; let name2 = 'TOM'; name = name2; console.log(name, name2) // TOM TOM
-
基本类型的比较是比较它们的值
let a = 1; let b = true; a == b // true a === b // false
-
基本类型的变量是存放在
stack
(栈)let a = 'tom'; let b = 123;
引用类型
-
引用类型的值是可变
const obj = { name: 'tom', age: 18 } obj.sex = 'man'; obj.getName = function () { console.log(this.name) }
-
引用类型的比较是引用的比较
const o1 = {}; const o2 = {}; o1 === o2 // false o1 == o2 // false
o1
、o2
引用分别放在堆内存中的2个对象 -
引用是类型的值是保存在堆内存中,指针存在栈中。
一道题实战
function addNum (num) {
num += 1;
return num
}
let count = 10;
let result = addNum(count);
console.log(count); // 10
console.log(result) // 11
function setPhone (obj) {
obj.phone = '123'
}
let p1 = new Object();
setPhone(p1)
console.log(p1) // {phone: '123'}
function setPhone (obj) {
obj.phone = '123'
// 创建一个新的对象
obj = new Object();
obj.phone = '456'
}
let p1 = new Object();
setPhone(p1)
console.log(p1) // {phone: '123'}
二、类型检测
typeof
- 只能检测基本数据类型 但是typeof null 是"object",不能检测复杂类型
instanceof
通过原型链去查找的- 返回的是false 或者true
- 可以识别内置对象类型、自定义类型及其父类型
- 不能识别标准类型,会返回false
- 不能识别undefined、null,会报错
constructor
属性 实例对象的constructor属性指向其构造函数。如果是内置类型,则输出function
数据类型(){[native code]}
;如果是自定义类型,则输出function
数据类型(){}
- 可以识别标准类型、内置对象类型及自定义类型
- 不能识别
undefined
、nul
,会报错,因为它俩没有构造函数
Object.prototype.toString()
返回[object 数据类型]
- 可以识别标准类型及内置对象类型
- 不能识别自定义类型
Array.isArray()
数组的检测
typeof
typeof
只能判断基本数据类型,不能判断引用数据类型(返回 object
)
let s = 'tom';
let num = 123;
let bool = true;
let sy = Symbol('tom')
let u;
let n = null;
let o = new Object();
typeof s // string
typeof num // number
typeof bool // boolean
typeof u // undefined
typeof sy // symbol
typeof n // object
typeof o // object
注意:typeof null
为object
为什么typeof null
为object
?
在 JavaScript 中 typeof null
的结果为 "object"
,这是从 JavaScript 的第一版遗留至今的一个 bug。在第一版的 JavaScript 中,变量的值被设计保存在一个 32 位的内存单元中。该单元包含一个 1 或 3 位的类型标志,和实际数据的值。类型标志存储在单元的最后。
数据类型 | 机器码标识 | 整数 | 1 | 浮点数 | 010 | 字符串 | 100 | 布尔 | 110 | undefined | -2^31(即全为1) | null | 全为0 | 对象(Object) | 000 |
---|
在判断数据类型时,是根据机器码低位标识来判断的,而null
的机器码标识为全0
,而对象的机器码低位标识为000
。所以typeof null
的结果被误判为Object
。
instanceof
- 通过原型链去查找的
- 返回的是false 或者true
- 可以识别内置对象类型、自定义类型及其父类型
- 不能识别标准类型,会返回false
- 不能识别undefined、null,会报错
'a' instanceof String -> false
12 instanceof Number -> false
true instanceof Boolean -> false
undefined instanceof Undefined ->报错
[] instanceof Array -> true
new Person instanceof Person -> true
new Person instanceof Object -> true
constructor
constructor
属性 实例对象的constructor属性指向其构造函数。如果是内置类型,则输出function
数据类型(){[native code]}
;如果是自定义类型,则输出function
数据类型(){}
- 可以识别标准类型、内置对象类型及自定义类型
- 不能识别
undefined
、nul
,会报错,因为它俩没有构造函数
('a').constructor -> function String(){[native code]}
(undefined).constructor) -> 报错
null).constructor -> 报错
{name: "jerry"}).constructor -> function Object(){[native code]}
(new Person).constructor -> function Person(){}
// 封装成一个类型识别函数
function type(obj){
var temp = obj.constructor.toString();
return temp.replace(/^function (\w+)\(\).+$/,'$1');
}
Object.prototype.toString()
Object.prototype.toString()
返回 [object 数据类型]
- 可以识别标准类型及内置对象类型
- 不能识别自定义类型
Object.prototype.toString.call('a') -> [object String]
Object.prototype.toString.call(undefined) -> [object Undefined]
Object.prototype.toString.call(null) -> Null
Object.prototype.toString.call({name: "jerry"})) ->object Object]
Object.prototype.toString.call(function(){}) -> [object Function]
Object.prototype.toString.call(new Person)) -> [object Object]
封装成一个类型识别函数
function type(obj){
return Object.prototype.toString.call(obj).slice(8,-1).toLowerCase();
}
Array.isArray()
数组检测
const a = [1,2,3]
Array.isArray(a) -> true
Array.isArray([]) -> true
三、其他
1.undefined
和null
的区别
-
null
和undefined
都是基本数据类型。 -
Undefined
类型只有一个值,就是undefined
。当声明的变量未初始化时,该变量的默认值是undefined
。所以一般地,undefined
表示变量没有初始化。 -
Null
类型只有一个值,就是null
。null
是javascript
语言的关键字,它表示一个特殊值,常用来描述"空值",逻辑角度看,null
值表示一个空对象指针
null == undefined -> ture
null === undefined -> false
null === null -> true
null == null -> true
undefined === undefined -> true
给一个全局变量赋值为`null`,相当于将这个变量的指针对象以及值清空,如果是给对象的属性赋值为`nul`l,或者局部变量赋值为`null`,相当于给这个属性分配了一块空的内存,然后值为`null`,` JS`会回收全局变量为`null`的对象。
给一个全局变量赋值为`undefined`,相当于将这个对象的值清空,但是这个对象依旧存在,如果是给对象的属性赋值为`undefined`,说明这个值为空值
扩展下:
声明了一个变量,但未对其初始化时,这个变量的值就是undefined,它是 JavaScript 基本类型 之一
var data;
console.log(data === undefined); //true
对于尚未声明过的变量,只能执行一项操作,即使用typeof操作符检测其数据类型,使用其他的操作都会报错。
//data变量未定义
console.log(typeof data); // "undefined"
console.log(data === undefined); //报错
值 null
特指对象的值未设置,它是 JavaScript 基本类型 之一。
值 null
是一个字面量,它不像undefined
是全局对象的一个属性。null
是表示缺少的标识,指示变量未指向任何对象。
// foo不存在,它从来没有被定义过或者是初始化过:
foo;
"ReferenceError: foo is not defined"
// foo现在已经是知存在的,但是它没有类型或者是值:
var foo = null;
console.log(foo); // null
2.JavaScript
中的变量在内存中的具体存储形式
- 基本类型 --> 保存在栈内存中,因为这些类型在内存中分别占有固定大小的空间,通过按值来访问。基本类型一共有6种:
Undefined
、Null
、Boolean
、Number
、String
和Symbol
- 引用类型 --> 保存在堆内存中,因为这种值的大小不固定,因此不能把它们保存到栈内存中,但内存地址大小的固定的,因此保存在堆内存中,在栈内存中存放的只是该对象的访问地址。当查询引用类型的变量时, 先从栈中读取内存地址, 然后再通过地址找到堆中的值。对于这种,我们把它叫做按引用访问。
3.JavaScript
对象的底层数据结构是什么
参考链接1 参考链接2
4.Symbol
类型在实际开发中的应用、可手动实现一个简单的 Symbol
ES6 的 Symbol 类型及使用案例
5.javascript隐性转换
在 JS 中基本数据类型之间的转化只有三种情况:
- 转换为布尔值
- 转换为数字
- 转换为字符串
上图中包含了所有转换规则,这里总结几点需要注意的地方:
NaN
是number
类型的数据,所以number
转其他类型时不要漏掉它。- 对象转字符串,结果为:
[object Object]
- 数组转数字的规则:空数组、只有一个数字元素的数组转为数字,其余情况均为
NaN
undefined
和null
转数字,分别为:NaN
和0
- 转为
Boolean
时,除了undefined
,null
,false
,NaN
,0
,-0
转为false
,其他的都转为true
,包括所有对象。
对于对象转换为基本类型时,其内部的原理如下:
- 如果已经是基本类型,直接返回
- 调用内置的
valueOf
、toString
方法,如果转换为基本类型,则返回转换的值 - 如果没有转换为基本类型,则会抛出错误
类型转换的一些小技巧
- 数据前置
+
号,转换为number
类型 - 数据与
0
相减,转换为number
类型 - 数据前置
!!
号,转换为Boolean
类型
coding
// 一、只有 + 两边有一边是字符串, 会把其它数据类型调用toString()方法转成字符串然后拼接
// + 是字符串连接符: String(1) + 'true' = '1true'
1 + "true" // '1true'
// 二、算术运算符+ :会把其它数据类型调用Number()方法转成数字然后做加法计算
// + 是算法运算 1 + Number(true)
1 + true // 2
// 1 + Number(undefined) = 1 + NaN = NaN
1 + undefined //NaN
// 1 + Number(null) = 1+ 0 =1
1 + null // 1
// 三、关系运算符 会把其他数据类型转换成number之后再比较关系
// 1.当关系运算符两边又一边是字符串时,会将其它数据类型使用Number()转换,然后比较关系
// Number('2') > 10 = 2 > 10 = false
'2' > 10 // false
// 2.当关系运算符两边都是字符串,此时同时转成number然后比较关系
// 重点:此时并不是按照Number()的形式转成数字,而是按照字符串对应的unicode编码转成数字
// 使用这个查看字符串的unicode编码: 字符串的charCodeAt(字符下标,默认为0)
// '2'.charCodeAt() > '10'.charCodeAt() = 50 > 49 = true
'2' > '10' // true
// 多个字符从左往右依次比较
// 先比较'a' 和 'b', ‘a' 与 ‘b'不等,则直接得出结果
"abc" > "b" //fasle
// 先比较'a' 和'a',两者相等,继续比较第二个字符'b'与‘a',得出结果 a.charCodeA = 97 b.charCodeB=98
"abc" > "aad" // true
// 3.特殊情况:如果数据类型是undefined与null,得出固定的结果
undefined == undefined // true
undefined === undefined // true
undefined == null // true
null == null // ture
null === null // ture
// 4.NaN与任何数据比较都是NaN
NaN == NaN // false
//四、复杂数据类型在隐式转换时,
// 如果是转换成number
//1、如果输入的值已经是一个原始值,则直接返回它
// 2、否则,如果输入的值是一个对象,则调用该对象的valueOf()方法,
// 如果valueOf()方法的返回值是一个原始值,则返回这个原始值。
// 3、否则,调用这个对象的toString()方法,如果toString()方法返回的是一个原始值,则返回这个原始值。
// 4、否则,抛出TypeError异常。
// 如果是转换成string
// 1、如果输入的值已经是一个原始值,则直接返回它
// 2、否则,调用这个对象的toString()方法,如果toString()方法返回的是一个原始值,则返回这个原始值。
// 3、否则,如果输入的值是一个对象,则调用该对象的valueOf()方法,
// 如果valueOf()方法的返回值是一个原始值,则返回这个原始值。
// 4、否则,抛出TypeError异常。
//复杂数据类型转number顺序如下
// 1.先使用valueOf()方法获取其原始值,如果原始值不是number类型,则使用toString()方法转换成string
// 2.再将string 转换成 number运算。
[1,2] == '1,2' // true [1,2]转换成string [1,2].valueOf() -> [1,2] [1,2].toString() -> '1,2'
var a = {}
a == "[object Object]" // true a.valueOf().toString() -> "[object Object]"
// 一道题
var a = ???
if(a==1 && a==2 &&a==3) {
console.log(1)
}
// 如何完善a,使其正确打印1
var a = {
i: 0, //声明一个属性i
valueOf: function() {
return ++ a.i //每次调用一次,让对象a的i属性自增一次并且返回
}
}
if(a==1 && a==2 &&a==3) { // 每次运算时都会调用一次a的valueOf方法
console.log(1)
}
// 五、逻辑非隐形转换与关系元算符隐式转换搞混淆
// 空数组的toString()方法会得到空字符串,而空对象的toString()方法会得到字符串'[object Object]'
// 1.关系运算法:将其它数据类型转成数字
// 2.逻辑非:将其它数据类型使用Boolean()转成布尔类型
// 以下八种情况转换为布尔类型会得到false
// 0、-0、NaN、undefined、null、空字符、false、document.all()
// [].valueOf().toString()得到空字符串 Number('') ==0
[] == 0 // ture
//本质是 ![] 逻辑非与表达式结果与0比较关系
// 1.逻辑非优先级高于关系运算法 ![] = false(空数组转布尔得到true,然后取反得到fasle)
// 2.false == 0 成立
![] == 0 // true
// 本质其实是空对象{} 与 !{} 这个逻辑非表达式结果做比较
//1.{}.valueOf().toString()得到字符串'[object Object]'
//2.!{} = false
//3.Number('[object Object]') -> NaN == Number(false) -> 0
{} == !{}
//引用类型数据存在堆中,栈中存储的是地址,所以他们的结果false
{} == {} // false
// 本质是空数组[] 与 ![] 这个逻辑表达式结果做比较
//1. [].valueOf().toString()得到空字符串
//2. ![] = false
//3.Number('') == Number(false) 成立 都是0
[] == ![] // true
[] == [] // false 引用类型数据在堆中,栈中存储的是地址,所以他们的结果都是fasle
{}.valueOf().toString() // [obejct Obejct]
[].valueOf().toString() //空字符串
掘金:你所忽略的js隐式转换
6.0.1+-0.2===0.3?
出现小数精度丢失的原因, JavaScript
可以存储的最大数字、最大安全数字, JavaScript
处理大数字的方法、避免精度丢失的方法
0.1 + 0.2 !== 0.3 详细讲解
7.== 和===
对于 ==
来说,如果对比双方的类型不一样的话,就会进行类型转换
假如我们需要对比 x
和 y
是否相同,就会进行如下判断流程:
-
首先会判断两者类型是否相同。相同的话就是比大小了
-
类型不相同的话,那么就会进行类型转换
-
会先判断是否在对比
null
和undefined
,是的话就会返回true
-
判断两者类型是否为
string
和number
,是的话就会将字符串转换为number
1 == '1' ↓ 1 == 1
-
判断其中一方是否为
boolean
,是的话就会把boolean
转为number
再进行判断'1' == true ↓ '1' == 1 ↓ 1 == 1
-
判断其中一方是否为
object
且另一方为string
、number
或者symbol
,是的话就会把object
转为原始类型再进行判断'1' == { name: 'tom' } ↓ '1' == '[object Object]'
流程图:
- 想了解更多的内容可以参考 标准文档。
对于
===
来说就简单多了,就是判断两者类型和值是否相同[] !== [] // true
8.var
、let
、const
区别
var
-
var
命令会发生“变量提升”现象,即变量可以在声明之前使用,值为undefined
。console.log(name); var name = 'tom'; // undefined
-
内层变量可能覆盖外层变量
var name = 'tom'; { var name = 'TOM' } name // 'TOM'
-
用来计数的循环变量泄露为全局变量
for(var i = 0; i < 5; i++) {}
-
var
不存在块级作用域{ var name = 'tom'; } console.log(name); // tom
let
-
声明的全局变量不会挂在顶层对象下面
-
let
所声明的变量,只在let
命令所在的代码块有效。{ let a = 1; var b = 2 } a // ReferenceError: a is not defined. b // 2
-
变量的使用,一定要在声明前,否则会报引用错误(
ReferenceError
)if (true) { let a; } console.log(a); // ReferenceError: a is not defined
-
let
和const
不允许在相同作用域内,重复声明同一个变量。//报错 function func () { let name = 'tom'; var name = 'TOM' } // 报错 function func () { let name = 'tom'; let name = 'TOM'; }
-
暂时性死区,只要块级作用域内存在
let
命令,它所声明的变量就“绑定”(binding
)这个区域,不再受外部的影响,在代码块内,使用let
命令声明变量之前,该变量都是不可用的。var a = 1; if (true) { a = 2; // ReferenceError let a; }
cosnt
-
声明的全局变量不会挂在顶层对象下面
-
const
声明之后必须马上赋值,否则会报错const name //Uncaught SyntaxError: Missing initializer in const declaration
-
const
声明一个只读的常量。一旦声明,常量的值就不能改变。const NAME = 'TOM' NAME = 'tom'; // TypeError: Assignment to constant variable.
-
const
命令声明的常量也是不提升,同样存在暂时性死区,只能在声明的位置后面使用。
9.var
、let
、const
原理
-
var
会直接在栈内存里预分配内存空间,然后等到实际语句执行的时候,再存储对应的变量,如果传的是引用类型,那么会在堆内存里开辟一个内存空间存储实际内容,栈内存会存储一个指向堆内存的指针。
-
let
是不会在栈内存里预分配内存空间,而且在栈内存分配变量时,做一个检查,如果已经有相同变量名存在就会报错。
-
const
也不会预分配内存空间,在栈内存分配变量时也会做同样的检查。不过const存储的变量是不可修改的,对于基本类型来说你无法修改定义的值,对于引用类型来说你无法修改栈内存里分配的指针,但是你可以修改指针指向的对象里面的属性。
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!