【前端面试之道小册笔记】JS基础篇No.1
1、数据类型
原始值:boolean、null、undefined、number、string、symbol
2、类型转换:
转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'
},
[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
3、this
我们先来看几个函数调用的场景
function foo() {
console.log(this.a)
}
var a = 1
foo()
const obj = {
a: 2,
foo: foo
}
obj.foo()
const c = new foo()
接下来我们一个个分析上面几个场景
- 对于直接调用
foo
来说,不管foo
函数被放在了什么地方,this
一定是window
- 对于
obj.foo()
来说,我们只需要记住,谁调用了函数,谁就是this
,所以在这个场景下foo
函数中的this
就是obj
对象 - 对于
new
的方式来说,this
被永远绑定在了c
上面,不会被任何方式改变this
说完了以上几种情况,其实很多代码中的 this
应该就没什么问题了,下面让我们看看箭头函数中的 this
function a() {
return () => {
return () => {
console.log(this)
}
}
}
console.log(a()()())
首先箭头函数其实是没有 this
的,箭头函数中的 this
只取决包裹箭头函数的第一个普通函数的 this
。在这个例子中,因为包裹箭头函数的第一个普通函数是 a
,所以此时的 this
是 window
。另外对箭头函数使用 bind
这类函数是无效的。
最后种情况也就是 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 fn1() {
return function() {
return fn.apply()
}.apply(a)
}
fn2()
可以从上述代码中发现,不管我们给函数 bind
几次,fn
中的 this
永远由第一次 bind
决定,所以结果永远是 window
。
let a = { name: 'yck' }
function foo() {
console.log(this.name)
}
foo.bind(a)() // => 'yck'
以上就是 this
的规则了,但是可能会发生多个规则同时出现的情况,这时候不同的规则之间会根据优先级最高的来决定 this
最终指向哪里。
首先,new
的方式优先级最高,接下来是 bind
这些函数,然后是 obj.foo()
这种调用方式,最后是 foo
这种调用方式,同时,箭头函数的 this
一旦被绑定,就不会再被任何方式所改变。
如果你还是觉得有点绕,那么就看以下的这张流程图吧,图中的流程只针对于单个规则。
![](https://img2018.cnblogs.com/blog/1232209/201905/1232209-20190513122512056-1406462570.png)
补充思考:call&apply&bind
1、call() 、apply()可以看作是某个对象的方法,通过调用方法的形式来间接调用函数,让函数在某个指定的对象下执行。bind() 就是将某个函数绑定到某个对象上
2、bind() 方法会返回执行上下文被改变的函数而不会立即执行,而前两者是直接执行该函数
3、bind虽然只返回一个函数并不调用,但是仍然可以传参,使用bind()方法使函数拥有预设的初始参数,这些参数会排在最前面,传给绑定函数的参数会跟在它们后面
1 function list(){ 2 // 让类数组arguments拥有数组的方法slice,这个函数实现了简单把类数组转换成数组 3 return Array.prototype.slice.call(arguments); 4 } 5 6 list(1,2,3);//[1,2,3] 7 8 //给list绑定一个预设参数4 9 var list1 = list.bind(undefined,4); 10 11 list1();//[4] 12 13 list1(1,2,3);//[4,1,2,3]