结束
一次完整的面试流程就是这样啦,小编综合了腾讯的面试题做了一份前端面试题PDF文档,里面有面试题的详细解析,分享给小伙伴们,有没有需要的小伙伴们都去领取!
数组扁平化的方法
//使用ES6中的Array.prototype.flat方法
arr.flat(Infinity)
//使用reduce的方式
function arrFlat(arr) {
return arr.reduce((pre, cur) => {
return pre.concat(Array.isArray(cur) ? arrFlat(cur) : cur)
}, [])
}
//使用递归加循环的方式
function arrFlat(arr) {
let result = []
arr.map((item, index) => {
if (Array.isArray(item)) {
result = result.concat(arrFlat(item))
} else {
result.push(item)
}
})
return result
}
//将数组先变成字符串,再复原 toString()
//这种方法存在缺陷,就是数组中元素都是Number或者String类型的才能展开
function arrFlat(arr) {
return arr.toString().split(',').map(item=> +item)
}
数组去重
定义去重数据
let arr = [1, 1, "1", "1", null, null, undefined, undefined, /a/, /a/, NaN, NaN, {}, {}, [], []]
我们先看下几种不能去掉重复的引用数据类型的写法
// 使用 Set
let res = [...new Set(arr)]
console.log(res)
这种方法虽然很简洁,但是使用该种方法我们可以看到里面的引用数据类型并没有能成功去重,只能去除基本数据类型
//使用filter
let res = arr.filter((item, index) => {
return arr.indexOf(item) === index
})
console.log(res)
//使用reduce
let res = arr.reduce((pre, cur) => {
return pre.includes(cur) ? pre : [...pre, cur]
}, [])
console.log(res)
使用该两种方法也和上面的方法一样,不能去掉引用数据类型。
我们再来看一下如何去除引用类型的重复值
利用对象的hasOwnProperty
方法进行判断对象上是否含有该属性,如果含有则过滤掉,不含有则返回新数组中
let obj = {}
let res = arr.filter(item => {
if (obj.hasOwnProperty(typeof item + item)) {
return false
} else {
obj[typeof item + item] = true
return true
}
})
console.log(res)
这次可以看到成功的将引用数据类型也去掉了。
除了以上这几种方法,还有一些循环遍历的方法也是类似的
类数组变成数组
类数组是具有length属性,但不具有数组原型上的方法。 比如说arguments,DOM操作返回的结果就是类数组。那么如何将类数组变成数组呢
-
Array.from(document.querySelectorAll('div'))
-
Array.prototype.slice.call(document.querySelectorAll('div'))
-
[...document.querySelectorAll('div')]
数据类型检测
typeof 1 // number
typeof '1' // string
typeof undefined // undefined
typeof true // boolean
typeof Symbol() // symbol
上面的几种类型都能正确的检测,但是引用数据类型除了函数都会显示为object
,而且对于 typeof null
也是 object
这是历史遗留下的bug,因为怕影响到一些现有的web项目,所以一直没有修复这个bug。
当检测引用数据类型的时候,用instanceof
比较好,它会基于原型链进行查询,如果查询结果在原型链中,就会返回true。
Object.prototype.toString.call(检测数据类型最佳方案)
调用Object原型上的toString()方法,并且通过call改变this指向。返回字符串 ,我们看看八种数据类型分别返回的结果
function checkType(param) {
return Object.prototype.toString.call(param)
}
console.log(checkType(123)) //[object Number]
console.log(checkType("123")) //[object String]
console.log(checkType(true)) //[object Boolean]
console.log(checkType({ a: 123 })) //[object Object]
console.log(checkType(() => {})) //[object Function]
console.log(Symbol(1)) //Symbol(1)
console.log(null) //null
console.log(undefined) //undefined
我们再对上述函数进行一下处理
function checkType(param) {
return Object.prototype.toString.call(param).slice(8, -1).toLowerCase()
}
console.log(checkType(1)) // number
Object.is和===的区别
Object.is在严格等于上的基础修复了一些特殊情况下的错误,比如NaN 不等于 NaN
function is(x, y){
if(x === y){
// 1/+0 = +Infinity 1/-0 = -Infinity 这两个是不相等的
// 当 x和y都等于0的时候,就对x/0和y/0做判断
return x !== 0 || y !== 0 || x / 0 === y / 0
}
}
和=的区别和隐式数据类型转化
===是严格相等,左右两边不仅值要相等,类型也要相等,例如'1'===1
的结果是false,因为左边是string,右边是number。
==只要值相等就会返回true,而且使用==时会发生隐式类型转化
, 在js中,当运算符在运算时,如果两边数据不统一,CPU就无法计算,这时我们编译器会自动将运算符两边的数据做一个数据类型转换,转成一样的数据类型再计算 。
-
转成string类型:+ 字符串连接符如
1 + "1" = "11"
-
转成number类型: ++、–(自增自减运算符) + 、-、*、/、%(加减乘除取余算术运算符) >、 <、 >=、 <=、 ==、 !=、 =、 ! (关系运算符)
let i = "1"
console.log(++i) // 2
-
转成boolean类型 : !(逻辑非运算符取反操作),使用Boolean转化除了下面这八种情况得到false以外,其它的情况都转为true 。
0、-0、NaN、undefined、null、“”(空字符串)、false、document.all()
-
如果其中一方为Object,且另一方为String、Number或者Symbol,会将Object转换成字符串,再进行比较
例子:
//字符串连接符
console.log(1 + 'true')// +是字符串连接符, String(1) + 'true',打印出'1true'
//算术运算符
console.log(1 + true) // +是算术运算符,true被Number(true)->1,打印出2
console.log(1 + undefined) // 1 + Number(undefined) -> 1 + NaN, 打印NaN
console.log(1 + null) // 1 + Number(null) -> 1 + 0,打印出1
//关系运算符
// 一边数字一边字符串,Number("2")
// 2 > 5,打印false
console.log("2" > 5)
// 两边字符串,调用"2".charCodeAt() -> 50
// "5".charAtCode()-> 53, 打印false
console.log("2" > "5")
//多个字符串从左往右匹配,也是调用charCodeAt方法进行比较
//比较"a".charCodeAt() < "b".charCodeAt(),打印false
console.log("abc" > "b")
// 左边第一个"a"和右边第一个"a"的unicode编码相等
// 继续比较两边第二个字符, "b" > "a",打印true
console.log("abc" > "aaa")
//无视上述规则自成体系
console.log(NaN == NaN) // NaN和任何数据比较都是 false
console.log(undefined == undefined) //true
console.log(undefined === undefined) //true
console.log(undefined == null) //true
console.log(undefined === null) //false
对于复杂的数据类型,比如对象和数组
对象和数组和字符串类型比较:先使用valueOf() 取得原始值,如果原始值不是number类型,则用toString()方法转成字符串类型valueOf -> toString
//发生了a.valueOf().toString()的转化,打印true
console.log([1,2] == "1,2")
// 发生了a.valueOf().toString()的转化,打印true
let a = {}
console.log(a == "[object Object]")
对象转原始类型,会调用内置的[ToPrimitive]
函数,对于该函数而言,其逻辑如下:
-
如果有设置
Symbol.toPrimitive()
方法,会优先调用并返回数据 -
调用
valueOf()
,如果转换为原始类型,则返回 -
调用
toString()
,如果转换为原始类型,则返回 -
如果没有返回原始类型,则报错
让我们来看俩个例子👇
let obj = {
value: 3,
valueOf() {
return 4
},
toString() {
return 5
},
return 6
},
}
console.log(obj + 1) //打印7
让 if(a ==1 && a == 2 && a == 3)
成立
let a = {
value: 0,
valueOf() {
return ++a.value
},
}
// 每次调用这个a对象的时候都会在0的基础上加1,调用3次后就变成了3
console.log(a == 1 && a == 2 && a == 3) //true
如果是数组和对象与number类型比较,先用valueOf取得原始值,原始值不是number类型则调用toString,然后再将字符串类型用Number转成数字类型,调用顺序valueOf() -> toString() -> Number()
空数组
的toString()方法会得到空字符串,而空对象
的toString()方法会得到字符串[object Object]
//发生了这样的转化:Number([].valueOf().toString()),打印true
console.log([] == 0)
//逻辑非运算符优先级大于关系运算符
//空数组转布尔得到true,然后取反得到false
//false = 0 ,打印true
console.log(![] == 0)
//左边:{}.valueOf().toString()得到”[object Object]“,Number(”[object Object]“)->NaN
//右边:!{}得到false ,Number(false) -> 0
//两边不相等,打印false
console.log({} == !{})
//左边:[].valueOf().toString()得到空字符串
//右边:![] 得到false
// Number("") = Number(false) 两边都为0
//打印true
console.log([] == ![])
//因为引用数据类型存储在堆中的地址,左边和右边分别属于两块不同的空间
//他们地址不相同,所以两边不相等
//下面两种情况都打印false
console.log([] == [])
console.log({} == {})
记录遇到的一个另一个相关问题
下面这三个的打印结果
//typof null返回的是object
console.log(typeof null)
//从右往左看,先看右边的typeof null整体,返回object之后
//再将整体看成typeof object
//打印结果为string,原因是typeof null返回的是object字符串
console.log(typeof typeof null)
//到这里也是从右往左看,相当于typeof string
//结果打印是string
console.log(typeof typeof typeof null)
实现一个instanceof
function myInstanceof(left,right) {
if(typeof left !== 'object' || left === null) return false
//获取原型
let proto = Object.getPrototypeOf(left)
while(true){
//如果原型为null,则已经到了原型链顶端,判断结束
if(proto === null) return false
//左边的原型等于右边的原型,则返回结果
if(proto === right.prototype) return true
//否则就继续向上获取原型
proto = Object.getPrototypeOf(proto)
}
}
实现继承
ES5中实现继承
//实现一下继承
function Parent() {
this.name = "大人"
this.hairColor = "黑色"
}
function Child() {
Parent.call(this)
this.name = "小孩"
}
Child.prototype = Object.create(Parent.prototype)
//将丢失的构造函数给添加回来
Child.prototype.constructor = Child
let c1 = new Child()
console.log(c1.name, c1.hairColor) //小孩,黑色
console.log(Object.getPrototypeOf(c1))
console.log(c1.constructor) //Child构造函数
let p1 = new Parent()
console.log(p1.name, p1.hairColor) //大人,黑色
console.log(Object.getPrototypeOf(p1))
console.log(p1.constructor) //Parent构造函数
ES6中实现继承
// ES6的继承
class Parent {
constructor() {
this.name = "大人"
this.hairColor = "黑色"
}
}
class Child extends Parent {
constructor() {
super() //调用父级的方法和属性
this.name = "小孩"
}
}
let c = new Child()
console.log(c.name, c.hairColor) //小孩 黑色
let p = new Parent()
console.log(p.name, p.hairColor) //大人 黑色
如何在ES5环境下实现const
此处需要用到Object.defineProperty(Obj,prop,desc)
这个API
function _const (key, value) {
const desc = {
value,
writable:false
}
Object.defineProperty(window,key,desc)
}
_const('obj',{a:1}) //定义obj
obj = {} //重新赋值不生效
手写Call
//手写call
let obj = {
msg: "我叫王大锤",
}
function foo() {
console.log(this.msg)
}
// foo.call(obj)
//调用call的原理就跟这里一样,将函数挂载到对象上,然后在对象中执行这个函数
// obj.foo = foo
// obj.foo()
最后更多分享:前端字节跳动真题解析
开源分享:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】