图中1, 2, 3, 4 表示优先级
JavaScript有哪些数据类型,它们的区别?
JavaScript共有八种
数据类型,分别是 Undefined
、Null
、Boolean
、Number
、String
、Object
、Symbol
、BigInt
, 其中 Symbol
和 BigInt
是 ES6+
中新增的数据类型
Symbol: 代表创建后独一无二
且不可变
的数据类型,它主要是为了解决可能出现的全局变量冲突的问题。
BigInt: 是一种数字类型的数据,它可以表示任意精度
格式的整数,使用 BigInt 可以安全地存储和操作大整数,即使这个数已经超出了 Number 能够表示的安全整数范围。
栈: 原始数据类型
(Undefined、Null、Boolean、Number、String、Symbol、BigInt)
堆: 引用数据类型
(对象、数组和函数)
栈: 占据空间小
、大小固定
,属于被频繁使用数据;
堆: 占据空间大
、大小不固定
。如果存储在栈中,将会影响程序运行的性能
;引用数据类型在栈中存储了指针,该指针指向堆中该实体的起始地址。
数据类型检测的方式有哪些
typeof
能判断基础类型
, 数组、对象、null 都会被判断为 object
typeof 2 // number
typeof true // boolean
typeof 'str' // string
typeof undefined // undefined
typeof function(){} // function
typeof [] // object
typeof {} // object
typeof null // object
instanceof
可以正确判断对象
的类型,不能判断基本数据
类型, 其内部运行机制是判断在其原型链
中能否找到该类型的原型。
2 instanceof Number // false
'str' instanceof String // false
true instanceof Boolean // false
[] instanceof Array // true
function(){} instanceof Function // true
{} instanceof Object // true
constructor
虽然简单
和复杂数据
类型都能判断,但是构造函数
可以被手动
改变,所以也不是百分之百准确
(2).constructor === Number // true
(true).constructor === Boolean // true
('str').constructor === String // true
([]).constructor === Array // true
(function() {}).constructor === Function // true
({}).constructor === Object // true
function Fn(){};
Fn.prototype = new Array();
var f = new Fn();
console.log(f.constructor===Fn); // false
console.log(f.constructor===Array); // true
Object.prototype.toString.call()
使用 Object 对象的原型方法 toString 来判断数据类型:
var a = Object.prototype.toString;
a.call(2)
a.call('str')
a.call(true)
a.call(null)
a.call(undefined)
a.call([])
a.call(function(){})
a.call({})
同样是检测对象 obj
调用 toString
方法,obj.toString()
的结果和 Object.prototype.toString.call(obj)
的结果不一样,这是为什么?
这是因为 toString
是 Object
的原型方法,而 Array
、 function
等类型作为 Object
的实例,都重写了 toString
方法。不同的对象类型调用 toString 方法时,根据原型链
的知识,调用的是对应的重写
之后的 toString
方法(function
类型返回内容为函数体的字符串,Array
类型返回元素组成的字符串…),而不会去调用 Object
上原型 toString
方法(返回对象的具体类型),所以采用 obj.toString()
不能得到其对象类型,只能将 obj
转换为字符串类型;因此,在想要得到对象的具体类型时,应该调用 Object
原型上的 toString
方法。
判断数组的方式有哪些
通过 Object.prototype.toString.call() 做判断
Object.prototype.toString.call(obj).slice(8,-1) === 'Array';
通过原型链做判断
obj.__proto__ === Array.prototype;
通过 ES6 的 Array.isArray() 做判断
Array.isArray(obj);
通过 instanceof 做判断
obj instanceof Array
通过 Array.prototype.isPrototypeOf
Array.prototype.isPrototypeOf(obj)
intanceof 操作符的实现原理及实现
instanceof
用于判断构造函数的 prototype 属性是否出现在对象的原型链中的任何位置。
function myInstanceof(left, right) {
// 获取对象的原型
let proto = Object.getPrototypeOf(left)
// 获取构造函数的 prototype 对象
let prototype = right.prototype;
// 判断构造函数的 prototype 对象是否在对象的原型链上
while (true) {
if (!proto) return false;
if (proto === prototype) return true;
// 如果没有找到,就继续从其原型上找,Object.getPrototypeOf方法用来获取指定对象的原型
proto = Object.getPrototypeOf(proto);
}
}
为什么0.1+0.2 ! == 0.3,如何让其相等
在开发过程中遇到类似这样的问题:
let n1 = 0.1, n2 = 0.2
console.log(n1 + n2) // 0.30000000000000004
这里得到的不是想要的结果,要想等于0.3,就要把它进行转化:
(n1 + n2).toFixed(2) // 注意,toFixed为四舍五入
toFixed(num)
方法可把 Number
四舍五入为指定小数位数的数字。
不等于 0.3
的原因是二进制
相加的结果
如何获取安全的 undefined 值?
因为 undefined
是一个标识符,所以可以被当作变量来使用和赋值,但是这样会影响 undefined 的正常判断。表达式 void
___ 没有返回值,因此返回结果是 undefined。void 并不改变表达式的结果,只是让表达式不返回值。因此可以用 void 0
来获得 undefined。
typeof NaN 的结果是什么?
NaN
指“不是一个数字”
,NaN 是一个“警戒值”
,用于指出数字类型中的错误情况,即“执行数学运算没有成功,这是失败后返回的结果”。
typeof NaN; // "number"
NaN
是一个特殊值
,它和自身不相等
,是唯一一个非自反的值。 NaN !== NaN 为 true
。
isNaN 和 Number.isNaN 函数的区别?
函数 isNaN
接收参数后,会尝试将这个参数转换
为数值,任何不能被转换为数值的的值都会返回 true,因此非数字值传入也会返回 true
,会影响 NaN
的判断。
函数 Number.isNaN
会首先判断传入参数是否为数字,如果是数字再继续判断是否为 NaN
,不会进行数据类型的转换,这种方法对于 NaN
的判断更为准确。
== 操作符的强制类型转换规则?
对于 ==
来说,如果对比双方的类型不一样
,就会进行类型转换
。假如对比 x 和 y 是否相同,就会进行如下判断流程:
- 首先会判断两者类型是否
相同
, 相同的话就比较两者的大小; - 类型
不相同
的话,就会进行类型转换
; - 会先判断是否在对比
null
和undefined
,是的话就会返回true
- 判断两者类型是否为
string
和number
,是的话就会将字符串转换为number
- 判断其中一方是否为
boolean
,是的话就会把boolean
转为number
再进行判断 - 判断其中一方是否为
object
且另一方为string
、number
或者symbol
,是的话就会把object
转为原始类型再进行判断
其流程图如下:
其他值到字符串的转换规则?
Null
和 Undefined
类型 ,null
转换为 "null"
,undefined
转换为 "undefined"
,
Boolean
类型,true
转换为 "true"
,false
转换为 "false"
。
Number
类型的值直接转换,不过那些极小和极大的数字会使用指数形式。
Symbol
类型的值直接转换,但是只允许显式强制类型转换,使用隐式强制类型转换会产生错误。
对普通对象来说,除非自行定义 toString()
方法,否则会调用 toString()
来返回内部属性 [[Class]] 的值,如"[object Object]"。如果对象有自己
的 toString()
方法,字符串化时就会调用该方法并使用其返回值。
其他值到数字值的转换规则?
Undefined
类型的值转换为 NaN
。
Null
类型的值转换为 0
。
Boolean
类型的值,true
转换为 1
,false
转换为 0
。
String
类型的值转换如同使用 Number()
函数进行转换,如果包含非数字值则转换为 NaN
,空字符串为 0
。
Symbol
类型的值不能转换为数字,会报错。
对象(包括数组)
会首先被转换为相应的基本类型值
,如果返回的是非数字
的基本类型值,则再遵循以上规则将其强制转换为数字。
为了将值转换为相应的基本类型值,抽象操作会首先检查该值是否有 valueOf()
方法。如果有并且返回基本类型值,就使用该值进行强制类型转换。如果没有就使用 toString()
的返回值来进行强制类型转换。
如果 valueOf()
和 toString()
均不返回基本类型值,会产生 TypeError
错误。
其他值到布尔类型的值的转换规则?
以下这些是假值
:
- undefined
- null
- +0、-0 和 NaN
- ""
假值的布尔强制类型转换结果为 false
。从逻辑上说,假值列表以外的都应该是真值。
|| 和 && 操作符的返回值?
||
,第一个参数为 true
就返回第一个操作数的值,如果为 false
就返回第二个操作数的值。
&&
,则相反, true
返回第二个操作数的值,false
返回第一个操作数的值。
Object.is() 与比较操作符 “===”、“==” 的区别?
==
进行相等判断时,如果两边的类型不一致,则会进行强制类型转化
后再进行比较。
===
进行相等判断时,类型不一致时,不会
做强制类型准换,直接返回 false
。
使用 Object.is
来进行相等判断时,一般情况下和 ===
的判断相同,它处理了一些特殊的情况,比如 -0
和 +0
不再相等,两个 NaN
是相等的。
什么是 JavaScript 中的包装类型?
在 JavaScript
中,基本类型是没有属性和方法的,但是为了便于操作基本类型的值,在调用基本类型的属性或方法时 JavaScript 会在后台隐式
地将基本类型的值转换为对象
,如:
const a = "abc";
a.length; // 3
a.toUpperCase(); // "ABC"
在访问 'abc'.length
时,JavaScript 将 'abc'
在后台转换成 String('abc')
,然后再访问其 length
属性。
JavaScript也可以使用 Object
函数显式地将基本类型转换为包装类型:
var a = 'abc'
Object(a) // String {"abc"}
也可以使用 valueOf
方法将包装类型倒转成基本类型:
var a = 'abc'
var b = Object(a)
var c = b.valueOf() // 'abc'
看看如下代码会打印出什么:
var a = new Boolean( false );
if (!a) {
console.log( "Oops" ); // never runs
}
答案是什么都不会打印,因为虽然包裹的基本类型是 false
,但是 false
被包裹成包装类型后就成了对象,所以其非值为 false
,所以循环体中的内容不会运行。
JavaScript 中如何进行隐式类型转换?
首先要介绍 ToPrimitive
方法,这是 JavaScript 中每个值隐含的自带的方法,用来将值 (无论是基本类型值还是对象)转换为基本
类型值。如果值为基本
类型,则直接返回值本身
;如果值为对象
,其看起来大概是这样:
/**
* @obj 需要转换的对象
* @type 期望的结果类型
*/
ToPrimitive(obj,type)
type
的值为 number
或者 string
。
- 当
type
为number
时规则如下:
- 调用 obj 的
valueOf
方法,如果为原始值,则返回,否则下一步; - 调用 obj 的
toString
方法,后续同上; - 抛出
TypeError
异常。
- 当
type
为string
时规则如下:
- 调用 obj 的
toString
方法,如果为原始值,则返回,否则下一步; - 调用 obj 的
valueOf
方法,后续同上; - 抛出
TypeError
异常。
可以看出两者的主要区别在于调用 toString
和 valueOf
的先后顺序。默认情况下:
- 如果对象为
Date
对象,则type
默认为string
; - 其他情况下,
type
默认为number
。
总结上面的规则,对于 Date
以外的对象,转换为基本类型的大概规则可以概括为一个函数:
var objToNumber = value => Number(value.valueOf().toString())
objToNumber([]) === 0
objToNumber({}) === NaN
而 JavaScript 中的隐式类型转换主要发生在+、-、*、/以及==、>、<这些运算符之间。而这些运算符只能操作基本类型值,所以在进行这些运算前的第一步就是将两边的值用ToPrimitive转换成基本类型,再进行操作。
以下是基本类型的值在不同操作符的情况下隐式转换的规则 (对于对象,其会被ToPrimitive转换成基本类型,所以最终还是要应用基本类型转换规则):
- +操作符+操作符的两边有至少一个string类型变量时,两边的变量都会被隐式转换为字符串;其他情况下两边的变量都会被转换为数字。
1 + '23' // '123'
1 + false // 1
1 + Symbol() // Uncaught TypeError: Cannot convert a Symbol value to a number
'1' + false // '1false'
false + true // 1
- -、*、\操作符NaN也是一个数字
1 * '23' // 23
1 * false // 0
1 / 'aa' // NaN
- 对于**==**操作符 操作符两边的值都尽量转成number:
3 == true // false, 3 转为number为3,true转为number为1
'0' == false //true, '0'转为number为0,false转为number为0
'0' == 0 // '0'转为number为0
- 对于**<和>**比较符 如果两边都是字符串,则比较字母表顺序:
'ca' < 'bd' // false
'a' < 'b' // true
其他情况下,转换为数字再比较:
'12' < 13 // true
false > -1 // true
以上说的是基本类型的隐式转换,而对象会被ToPrimitive转换为基本类型再进行转换:
var a = {}
a > 2 // false
其对比过程如下:
a.valueOf() // {}, 上面提到过,ToPrimitive默认type为number,所以先valueOf,结果还是个对象,下一步
a.toString() // "[object Object]",现在是一个字符串了
Number(a.toString()) // NaN,根据上面 < 和 > 操作符的规则,要转换成数字
NaN > 2 //false,得出比较结果
又比如:
var a = {name:'Jack'}
var b = {age: 18}
a + b // "[object Object][object Object]"
运算过程如下:
a.valueOf() // {},上面提到过,ToPrimitive默认type为number,所以先valueOf,结果还是个对象,下一步
a.toString() // "[object Object]"
b.valueOf() // 同理
b.toString() // "[object Object]"
a + b // "[object Object][object Object]"
为什么会有BigInt的提案?
JavaScript中 Number.MAX_SAFE_INTEGER
表示最⼤安全数字,计算结果是 9007199254740991
,即在这个数范围内不会出现精度丢失(⼩数除外)。但是⼀旦超过这个范围,js就会出现计算不准确
的情况,这在⼤数计算的时候不得不依靠第三⽅
库进⾏解决,因此官⽅提出了 BigInt
来解决此问题。
object.assign和扩展运算法是深拷贝还是浅拷贝,两者区别
结论: 都为浅拷贝
扩展运算符:
let outObj = {
inObj: {a: 1, b: 2}
}
let newObj = {...outObj}
newObj.inObj.a = 2
console.log(outObj) // {inObj: {a: 2, b: 2}}
Object.assign():
let outObj = {
inObj: {a: 1, b: 2}
}
let newObj = Object.assign({}, outObj)
newObj.inObj.a = 2
console.log(outObj) // {inObj: {a: 2, b: 2}}
let、const、var的区别
块级作用域: 块作用域由 { }
包括,let
和 const
具有块级作用域,var
不存在块级作用域。块级作用域解决了 ES5
中的两个问题:
- 内层变量可能覆盖外层变量
- 用来计数的循环变量泄露为全局变量
变量提升: var
存在变量提升,let
和 const
不存在变量提升,即在变量只能在声明之后使用,否在会报错。
给全局添加属性: 浏览器的全局对象是 window
,Node
的全局对象是 global
。var
声明的变量为全局变量,并且会将该变量添加为全局对象的属性,但是 let
和 const
不会。
重复声明: var
声明变量时,可以重复声明变量,后声明的同名变量会覆盖之前声明的遍历。const
和 let
不允许重复声明变量。
暂时性死区: 在使用 let
、 const
命令声明变量之前,该变量都是不可用的。这在语法上,称为暂时性死区。使用 var
声明的变量不存在暂时性死区。
初始值设置: 在变量声明时,var
和 let
可以不用设置初始值。而 const
声明变量必须设置初始值。
指针指向: let
和 const
都是 ES6+
新增的用于创建变量的语法。 let
创建的变量是可以更改指针指向(可以重新赋值)。但 const
声明的变量是不允许改变指针的指向。
区别 | var | let | const |
---|---|---|---|
是否有块级作用域 | × | ✔️ | ✔️ |
是否存在变量提升 | ✔️ | × | × |
是否添加全局属性 | ✔️ | × | × |
能否重复声明变量 | ✔️ | × | × |
是否存在暂时性死区 | × | ✔️ | ✔️ |
是否必须设置初始值 | × | × | ✔️ |
能否改变指针指向 | ✔️ | ✔️ | × |
如果new一个箭头函数的会怎么样
箭头函数是 ES6
中的提出来的,它没有 prototype
,也没有自己的 this
指向,更不可以使用 arguments
参数,所以不能New一个箭头函数。
new
操作符的实现步骤如下:
- 创建一个对象
- 将构造函数的作用域赋给新对象(也就是将对象的__proto__属性指向构造函数的prototype属性)
- 指向构造函数中的代码,构造函数中的this指向该对象(也就是为这个对象添加属性和方法)
- 返回新的对象
所以,上面的第二、三步,箭头函数都是没有办法执行
的。
箭头函数与普通函数的区别
(1)箭头函数比普通函数更加简洁
- 如果没有参数,就直接写一个空括号即可
- 如果只有一个参数,可以省去参数的括号
- 如果有多个参数,用逗号分割
- 如果函数体的返回值只有一句,可以省略大括号
- 如果函数体不需要返回值,且只有一句话,可以给这个语句前面加一个void关键字。最常见的就是调用一个函数:
let fn = () => void doesNotReturn();
(2)箭头函数没有自己的this
箭头函数不会创建自己的this, 所以它没有自己的this,它只会在自己作用域的上一层继承this。所以箭头函数中this的指向在它在定义时已经确定了,之后不会改变。
(3)箭头函数继承来的this指向永远不会改变
var id = 'GLOBAL';
var obj = {
id: 'OBJ',
a: function(){
console.log(this.id);
},
b: () => {
console.log(this.id);
}
};
obj.a(); // 'OBJ'
obj.b(); // 'GLOBAL'
new obj.a() // undefined
new obj.b() // Uncaught TypeError: obj.b is not a constructor
对象obj的方法b是使用箭头函数定义的,这个函数中的this就永远指向它定义时所处的全局执行环境中的this,即便这个函数是作为对象obj的方法调用,this依旧指向Window对象。需要注意,定义对象的大括号{}是无法形成一个单独的执行环境的,它依旧是处于全局执行环境中。
(4)call()、apply()、bind()等方法不能改变箭头函数中this的指向
var id = 'Global';
let fun1 = () => {
console.log(this.id)
};
fun1(); // 'Global'
fun1.call({id: 'Obj'}); // 'Global'
fun1.apply({id: 'Obj'}); // 'Global'
fun1.bind({id: 'Obj'})(); // 'Global'
(5)箭头函数不能作为构造函数使用
构造函数在new的步骤在上面已经说过了,实际上第二步就是将函数中的this指向该对象。 但是由于箭头函数时没有自己的this的,且this指向外层的执行环境,且不能改变指向,所以不能当做构造函数使用。
(6)箭头函数没有自己的arguments
箭头函数没有自己的arguments对象。在箭头函数中访问arguments实际上获得的是它外层函数的arguments值。
(7)箭头函数没有prototype
(8)箭头函数不能用作Generator函数,不能使用yeild关键字
完结,撒花 ✿✿ヽ(°▽°)ノ✿
点赞 o( ̄▽ ̄)d 不迷路
收藏 ✋🏻 + 关注
谢谢老铁 ♪(・ω・)ノ