JS基础知识及常考面试题
1. 数据类型
原始(Primitive)类型
面试题:原始类型有哪几种?null 是对象吗?原始类型种类:boolean、number、string、null、undefined、symbol
原始类型存储的都是值,没有函数可以调用,true.toString()会强制转换成对象 String 类型
typof null 输出 object,但是是 JS 的一个 bug,最初设计使用 32 位系统,为了性能考虑使用低位存储变量的类型信息,000 开头代表是对象, null 表示全为 0,所以误判为 object。对象(Object)类型
面试题:对象类型和原始类型的不同之处?函数参数是对象会发生什么问题?原始类型存储的是值,对象类型存储的是地址(指针)。当我们将变量赋值给另一个变量时,复制的是原本变量的地址(指针),所以数据修改时,是修改存放在该地址(指针)上的值,导致两个变量的值都发生了变化。
函数传参是传递对象指针的副本,形参会被重新分配一个新的地址(指针),就和原本的对象没有关系了,两个变量的值就不同。类型判断
面试题:typeof 是否能正确判断类型?instanceof 能正确判断对象的原理是什么?typeof 除了 null 之外都可以显示正确的类型,typeof null 为 object,typeof 除了函数的对象,都会显示 object,typeof 函数会显示 function,所以 typeof 不能准确判断变量的类型。
instanceof 是通过原型链来判断对象的正确类型的,但是不能直接通过 instanceof 来判断原始类型,但是可以通过 Symbol.hasInstance 来改造。
1 | class PrimitiveString { |
类型转换
原始类型之间转换:转换为布尔值、转换为数字、转换为字符串注:在条件判断时,除了 undefined, null, false, NaN, ‘’, 0, -0,其他所有值都转为 true,包括所有对象。
对象转原始类型:会调用内置的 [[ToPrimitive]] 函数,该函数会优先调用 valueOf 方法,如果转换为基础类型就返回转换后的值,否则调用 toString 方法,如果两种方法都没有返回原始类型,则抛错。(可以重写 Symbol.toPrimitive 方法,该方法在转原始类型时调用的优先级最高)
toString()与 valueOf()的差别:
- toString()将数据转换成相应的字符串形式,参考类型转换表
- valueOf()返回自身的值,比如数组会返回数组,函数会返回函数
2.四则运算符
加法运算符:
如果其中一方是字符串,就把另一方也转换为字符串,例:1
1 + '1' -> '11'
如果一方不是字符串或数字,就将它转换成数字或字符串,例:
1
2true + true -> 2
4 + [1,2,3] -> '41,2,3'一元加法运算,快速获取 number 类型,例:
1
2+'1' -> 1
'a' + + 'b' -> 'aNaN'
- 其他的运算符:
只要其中一方是数字,另一方就转为数字
注:如果{}被放在运算符最前面,会当做代码块处理,如果{}被放在运算中,那么他首先会调用 toString 方法
1 | {} + [] => 0 |
3.比较运算符
如果是对象,通过 toPrimitive 转换对象,具体调用规则看上面的类型转换
如果是字符串,通过 unicode 字符索引来比较
4.this
面试题:如何正确判断 this?箭头函数的 this 是什么?
箭头函数没有 this,箭头函数中的 this 取决于包裹箭头函数的第一个普通函数的 this,就不能用过构造函数。
谁调用了函数,谁就是 this。多个 this 同时出现的情况下,this 的优先级:new foo() > foo.bind() > obj.foo() > foo()
5.== 和 ===
面试题:==和===有什么区别?
- == 类型不一样就会进行类型转换,相同就比大小。流程:
- 比对双方是否为 null 和 undefined,是则返回 true
- 比对双方类型是否为 string 和 number,是则将字符串转为 number,例:
1 | 1 == '1' -> 1 == 1 |
- 比对一方是否为 boolean,是则把 boolean 转为 number,例:
1 | '1' == true -> '1' == 1 |
- 判断一方是否为 object,另一方为 string、number、symbol,是则会 object 转为原始类型,例:
1 | '1' == { name:'xx' } -> '1' == '[object Object]' |
思考题: [] == ![]
1 | ![].valueOf() -> false |
- ===判断类型和值是否相同。
6. 闭包
面试题:什么是闭包?如何解决循环中 var 定义函数的问题?
定义:函数 A 的内部有一个函数 B,函数 B 可以访问函数 A 中的变量,函数 B 就是闭包。函数嵌套函数,返回一个函数,这个解释不完整,看下面的例子:
1 | function A() { |
经典面试题:
1 | for (var i = 1; i <= 5; i++) { |
输出全是 6 的原因:setTimeout 是个异步函数,所以 for 先全部执行完毕,再到 event loop 中执行 setTimeout,此时 i 已经是 6 了。解决方法:
- 方法 1:使用闭包,用立即执行函数包裹(关键!在 for 循环中定义了 5 个定时器,本来 5 个定时器会放到队列里等待执行,由于外面是立即执行函数,就会立即执行,并且把 i 传进去了,等到函数执行的时候,向上查找 i,正好在立即执行函数的作用域里找到了 i)
1 | for (var i = 1; i <= 5; i++) { |
1 | for (var i = 1; i <= 5; i++) { |
- 方法 2:使用 setTimeout 的第三个参数,当做定时器函数的参数
1 | for (var i = 1; i <= 5; i++) { |
- 方法 3:使用 let 来定义 i
7. 深浅拷贝
面试题:什么是浅拷贝?如何实现浅拷贝?什么是深拷贝?如何实现深拷贝?
- 浅拷贝只拷贝对象的第一层,如果接下去的值还有对象,则需要使用深拷贝。
- 用 Object.assign 和展开运算符…解决浅拷贝的问题,拷贝所有的属性值到新对象
- 实现深拷贝:使用 JSON.parse(JSON.stringify(object)),但是会忽略 undefined、symbol,不能序列化函数,也不能解决循环引用的对象,所以推荐使用 lodash 的深拷贝函数
1 | function deepClone(obj) { |
7. 原型
面试题:如何理解原型?如何理解原型链?
每个 JS 对象都有proto属性,这个属性实际上是个对象,这个属性指向原型,是为了让我们访问到内部属性 [[prototype]] 。原型的 constructor 属性指向构造函数,构造函数又通过 prototype 属性指回原型。
prototype 存在的意义是在 function 作为 constructor 用时(new 或 super)能复制到生成对象的proto上,对于一些内部方法(比如 toString、Math.max)不会作为 constructor,所以没有 prototype 是合理的(同时也没有[[Construct]]内部属性)
原型链就是多个对象通过proto的方式连接了起来,obj 可以访问 valueOf 就是因为 obj 通过原型链找到 Object,从而找到了 valueOf 函数,一个类型的原型对象等于另一个类型的实例,伪代码 Subtype.prototype = new SuperType()。
1 | __proto__ :{ |