学习来源支持正版:juejin.im/book/5bdc71…
原始(Primitive)类型
在 JS 中,存在着 7 种原始值,分别是:
booleannullundefinednumberstringsymbolbigint
首先原始类型存储的都是值,是没有函数可以调用的,比如 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,是的话就会将字符串转换为number1 == '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指向,