学习来源支持正版:juejin.im/book/5bdc71…
原始(Primitive)类型
在 JS 中,存在着 7 种原始值,分别是:
boolean
null
undefined
number
string
symbol
bigint
首先原始类型存储的都是值,是没有函数可以调用的,比如 undefined.toString()
null.toString()
'1'.toString()
是可以使用,被强制转换成了 String
类型也就是对象类型,
null
来说,很多人会认为他是个对象类型,其实这是错误的。虽然 typeof null
会输出 object
,但是这只是 JS 存在的一个悠久 Bug。在 JS 的最初版本中使用的是 32 位系统,为了性能考虑使用低位存储变量的类型信息,000
开头代表是对象,然而 null
表示为全零,所以将它错误的判断为 object
。虽然现在的内部类型判断代码已经改变了,但是对于这个 Bug 却是一直流传下来
对象类型
原始类型存储的是值,对象类型存储的是地址(指针)。当你创建了一个对象类型的时候,计算机会在内存中帮我们开辟一个空间来存放值,但是我们需要找到这个空间,这个空间会拥有一个地址(指针)
typeof vs instanceof
typeof
对于原始类型来说,除了null
都可以显示正确的类型
typeof
对于对象来说,除了函数都会显示object
instanceof
判断类型方法
class PrimitiveString {
static [Symbol.hasInstance](x) {
return typeof x === 'string'
}
}
console.log('hello world' instanceof PrimitiveString) //true
类型转换
首先我们要知道,在 JS 中类型转换只有三种情况,分别是:
- 转换为布尔值
- 转换为数字
- 转换为字符串
转Boolean
在条件判断时,除了 undefined
, null
, false
, NaN
, ''
, 0
, -0
,其他所有值都转为 true
,包括所有对象。
[]==![] // ![]等于true,true和空数组比,此时数组是对象,所以都是true,但是若完全等于[]===![]时,两者不进行转换,所以true和[]比较是false
对象转原始类型
对象在转换类型的时候,会调用内置的 [[ToPrimitive]]
函数,对于该函数来说,算法逻辑一般来说如下:
- 如果已经是原始类型了,那就不需要转换了
- 调用
x.valueOf()
,如果转换为基础类型,就返回转换的值 - 调用
x.toString()
,如果转换为基础类型,就返回转换的值 - 如果都没有返回原始类型,就会报错
当然你也可以重写 Symbol.toPrimitive
,该方法在转原始类型时调用优先级最高。
let a = {
valueOf() {
return 0
},
toString() {
return '1'
},
[Symbol.toPrimitive]() {
return 2
}
}
1 + a // => 3
四则运算符
加法运算符不同于其他几个运算符,它有以下几个特点:
- 运算中其中一方为字符串,那么就会把另一方也转换为字符串
- 如果一方不是字符串或者数字,那么会将它转换为数字或者字符串
1 + '1' // '11'
true + true // 2
4 + [1,2,3] // "41,2,3"
如果你对于答案有疑问的话,请看解析:
- 对于第一行代码来说,触发特点一,所以将数字
1
转换为字符串,得到结果'11'
- 对于第二行代码来说,触发特点二,所以将
true
转为数字1
- 对于第三行代码来说,触发特点二,所以将数组通过
toString
转为字符串1,2,3
,得到结果41,2,3
另外对于加法还需要注意这个表达式 'a' + + 'b'
'a' + + 'b' // -> "aNaN"
因为 + 'b'
等于 NaN
,所以结果为 "aNaN"
,你可能也会在一些代码中看到过 + '1'
的形式来快速获取 number
类型。
那么对于除了加法的运算符来说,只要其中一方是数字,那么另一方就会被转为数字
4 * '3' // 12
4 * [] // 0
4 * [1, 2] // NaN
比较运算符
- 如果是对象,就通过
toPrimitive
转换对象 - 如果是字符串,就通过
unicode
字符索引来比较
let a = {
valueOf() {
return 0
},
toString() {
return '1'
}
}
a > -1 // true
在以上代码中,因为 a
是对象,所以会通过 valueOf
转换为原始类型再比较值。
== vs ===
对于
==
来说,如果对比双方的类型不一样的话,就会进行类型转换,
- 首先会判断两者类型是否相同。相同的话就是比大小了
- 类型不相同的话,那么就会进行类型转换
- 会先判断是否在对比
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: 'yck' } ↓ '1' == '[object Object]'
===
来说就简单多了,就是判断两者类型和值是否相同。
this
箭头函数中的
this
只取决包裹箭头函数的第一个普通函数的this
let a = {}
let fn = function () { console.log(this) }
fn.bind().bind(a)() // => ?
如果你认为输出结果是 a
,那么你就错了,其实我们可以把上述代码转换成另一种形式
// fn.bind().bind(a) 等于
let fn2 = function fn1() {
return function() {
return fn.apply()
}.apply(a)
}
fn2()
可以从上述代码中发现,不管我们给函数 bind
几次,fn
中的 this
永远由第一次 bind
决定,所以结果永远是 window
。
var a = 1
var b = {
a: 2,
c: () => {
var a = 3
console.log('c',a,this.a)
},
d: function() {
var a = 4
console.log('d',a,this.a)
},
e: () => {
var a = 5
return () => {
var a=6
console.log('e',a,this.a)
}
},
f: function() {
var a = 5
return () => {
var a=6
console.log('f',a,this.a)
}
},
}
b.c() // c 3 1 this指向从箭头函数向其父级第一个不是箭头函数的地方
var m = b.c
m() // c 3 1 // this指向从箭头函数向其父级第一个不是箭头函数的地方
b.d() // d 4 2 //this指向当前执行对象b本身的this指向
var n = b.d
n() // d 4 1 // this指向当前声明n父级的this指向
b.e()() // e 6 1 // this指向从箭头函数向其父级第一个不是箭头函数的地方
var p = b.e
p()() // e 6 1 // this指向从箭头函数向其父级第一个不是箭头函数的地方
var p = b.e()
p() // e 6 1 // this指向从箭头函数向其父级第一个不是箭头函数的地方
b.f()() // f 6 2 // 执行的箭头函数找到父级f不是箭头函数为止的this
var o = b.f
o()() // f 6 1 // 执行箭头函数找到父级本身的this指向
var o = b.f()
o() // f 6 2 // 执行箭头函数找到父级的内部匿名函数的this指向,