基本类型(Primitive)
面试题 基本类型有哪几种?null是对象吗?
js基本类型
boolean
null
undefined
number
string
symbol
基本类型存储的都是值,是没有函数可以调用的,比如undefined.toString(),你可能会有疑问,'1'.toString()是可以使用的,'1'已经不是基本类型了,而是被强制转化成了String类型,也就是对象类型,所以可以调用toString函数。
除了会在必要的情况下强转类型意外,原始类型还有一些坑。
其中js的number浮点类型的,在使用过程中遇到某些bug,比如0.1+0.2 !== 0.3 返回的是false,string类型是不可变的,无论你在string类型上调用任何方法,都不会对值有改变。
另外对于null来说,很多认为它是一个对象类型,这是不对的,虽然typeof 返回的是object,但它只是js中一直存在的bug,js最初的版本是32位系统,为了性能的考虑使用低位存储变量的类型信息,000开头的是对象,然而null表示为全零,所以将他错误的判断为对象,虽然现在内部类型的代码已经改变,但对于这个bug一直保存了下来。
对象类型
面试题 对象类型和基本类型的不同指出?函数参数是对象会发生什么问题?
在js中,除了基本类型就是对象类型了,对象类型和基本类型不同的是,基本类型存储的是值,对象类型存储的是指针,当你创建一个对象类型的时候,计算机内存会帮我们开辟一个空间来存放值,但我们需要找到这个空间,这个空间会拥有一个指针。
const a = []
对于常量a来说,假设地址为#0001,在该地址存储了[],常量地址存储了地址#0001,再看以下代码
const a = []
const b = a
console.log(b)
当我们将变量赋给另一个变量时,复制的是原本变量的指针,当我们修改存放的地址的值,也导致了两个变量的值发生了改变。
接下来我们看下函数参数是对象的情况
function test(person){
person.age =26
person ={
name:'han',
age:30
}
return person
}
const p1 = {
name:'xiao',
age:'34'
}
const p2 = test(p1)
console.log(p1)
console.log(p2)
对于以上代码你能否写出正确的结果,接下来我们来分析一下
首先函数传参是传递的指针副本
到函数内部修改参数这步,当前p1的值也被修改了
当我们重新为person分配一个对象时,就产生了分歧
所以person有了一个新的指针,也就和p1没什么关系了,导致了最后两个变量的值时不同的
typeof vs instanceof
面试题 typeof能否正确判断类型?instanceof正确对象的原理是什么? typeof对于原始类型来说,除了null都可以显示正确的类型。
typeof 1 // number
typeof '1' //string
typeof undefined // 'undefined'
typeof true // 'boolean'
typeof Symbol() // 'symbol'
typeof 对于对象来说,除了函数都会显示 object,所以说 typeof 并不能准确判断变量到底是什么类型
typeof [] // 'object'
typeof {} // 'object'
typeof console.log // 'function'
如果我们想判断一个对象的正确类型,这时候我们要考虑使用instanceof,因为内部机制时通过原型链进行判断的
const Person = function(){
}
const p1 = new Person()
p1 instanceof Person
var str ='person'
str instanceof String
var str1 = new String('xiao')
str1 instanceof str1
对于基本类型来说,你通过instanceof来判断类型是不行的,当然我们是有办法让instanceof判断基本类型的
class PrimitiveString {
static [Symbol.hasInstance](x) {
return typeof x === 'string'
}
}
console.log('hello world' instanceof PrimitiveString) // true
你可能不知道 Symbol.hasInstance 是什么东西,其实就是一个能让我们自定义 instanceof 行为的东西,以上代码等同于 typeof 'hello world' === 'string',所以结果自然是 true 了。这其实也侧面反映了一个问题, instanceof 也不是百分之百可信的。
类型转换
首先我们知道js中类型转换只有三种情况:
转换为布尔值
转换为数字
转换为字符串
我们先看一下下面的例子
原始值 转化目标 结果
number 布尔值 除了0,-0,NaN都为true
string 布尔值 除了字符串都为true
undefined null 布尔值 false
引用类型 布尔值 true
number 字符串 5=>'5'
布尔 函数 symbol 字符串 'true'
数组 字符串 [1,2]=>1,2
对象 字符串 "[object object]"
字符串 数字 '1'=>1,'a'=>NaN
数组 数字 空数组转化为0,有一个元素且为数字
转化为数字,其他情况则为NaN
null 数字 0
除了数组的引用类型 数字 NaN
symbol 数字 报错
转boolean
在条件判断时,除了undefined,null,false,NaN,'',0,-0,其他所有的值都为true,包括所有对象
对象转换原始类型 对象在转换类型的时候,会调用内置的[[ToPrimitive]]函数,对于该函数来说,算法逻辑一般如下:
如果已经时原始类型了,那就不需要转换了
如果需要转字符串类型就调用x.toString(),转换为基础类型的话就返回转换的值。不转换字符串类型的话就先调用valueof,结果不是基础类型的话再调用tostring
调用x.valueof(),如果转换为基础类型,就返回转换的值
如果没有返回原始类型,就会报错
当然你也可以重写Symbol.toPrimitive,该方法在转原始类型时调用优先级最高
let a={
valueof(){
return 0
},
toString(){
return 1
},
[].toPrimitive(){
return 2
}
}
1+a //3
四则运算符
加法运算符不同于其他几个运算符,它有以下特点
运算中其中一方为字符串,那么就会把另一方转化为字符串
如果一方不是字符串或者数字,那么会将数字转化为字符串
1+'1' //1
true+true //2
4+[1,2,3] //41,2,3
如果对于答案有疑问的话,请看解析:
对于第一行代码来说,触发特点一,所以将数字以1转化为字符串,结果得到‘11’
对于第二行代码来说,触发特点二,所以将true转为数字1
对于第三行代码来说,触发特点二,将数组通过toString转化为字符串1,2,3,得到结果41,2,3
另外对于加法还需要主义这个表达式
'a'++'b' //"aNaN"
应为+'b'等于NaN,所以结果为“NaN”,你可能也会在一些代码中看到过+‘1’的形式来快速获取number类型 对了除了加法的运算符来说,只要其中一方时数字,那么另一方就会转化为数字
4*'3' //12
4*[] //0
4*[1,2] //NaN
比较运算符
1.如果是对象,就通过toPrimitive转化对象 2.如果是字符串,就通过unicode字符索引比较
let a={
valueof(){
return 0
},
toString(){
return '1'
}
}
a>-1 //true
在以上代码中,因为a是对象,所以会通过valueof转换为原始类型再比较
this
面试题 如何正确判断this?箭头函数this是什么? this是很多人混淆的概念,但是其实它一点都不难,只是网上很多文章把简单的东西说的复杂了,在这一小节中,你一定会彻底明白this这个概念的
function foo(){
console.log(this.a)
}
var a=1
foo()
const obj = {
a:2,
foo:foo
}
obj.foo()
const c = new foo()
接下来我们一个个分析上面几个场景
对于直接调用foo来说,不管函数被放在什么地方,this一定是window
对于obj.foo()来说,我们只需记住,谁调用了函数,谁就是this,所以在这个场景下foo函数中的this就是obj
对于new方式来说,this永远绑定在了c上面,不会被任何方式改变this
首先箭头函数其实没有this的,箭头函数中的this只取决包裹箭头函数的第一个普通函数的 this
function a(){
return ()=>{
return ()=>{
return this
}
}
}
console.log(a()())
说完了上面的几种情况,其实很多代码中的this应该没什么问题了,下面我们看看箭头函数中的this,在这个例子中,因为包裹箭头函数的第一个普通函数是a,所以此时的this是window。另外对箭头函数使用bind,apply,call这类函数是无效的。
最后情况也就是bind这些改变上下文的api了,对于这些函数来说,this取决于第一个参数,如果第一个参数为空,那么就是window
那么说到bind,如果对一个函数进行多次bind,那么上下文会是什么呢?
let a ={}
let fn = function(){
console.log(this)
}
fn.bind().bind(a)()
如果你认为输出的结果是a,那么就错了,其实我们可以换成另一种形式
//fn.bind().bind(a)等于
let fn2 = function(){
return function(){
return fn.apply()
}
}
fn2()
可以从上述代码中发现,不管我们给函数bind几次,fn中的this永远由第一次bind决定,所以结果永远是window
let a = {
name:'han'
}
function foo(){
console.log(this.name)
}
foo.bind(a)() //han
以上就是this的规则了,但是可能会发生多个规则同时出现的情况,这时候不同的规则之间会根据优先级最高的来决定this最终指向哪里。
首先,new的优先级方式最高,接下来是bind这些函数了,然后obj.foo()这种调用方式,最后foo这种调用方式,同时,箭头函数的this一旦绑定,就不会再被任何方式有所改变