面试题 == 和 === 有什么区别 对于== 来说,如果对比双方的类型不一样的话,就会进入类型转换。
假设我们需要对比x和y是否相同,就会进入如下判断流程:
1.首先会判断两者类型是否相同。相同的话就比较大小
2.类型不相同的话,就进行类型转换
3.会先判断是否在对比null和undefined,是的话就返回true
4.判断两者类型shi否为string和number,是的话就会将字符串转换为number
5.判断其中一方是否为布尔值,是的话就将布尔值转化为number再进行判断
6.判断其中一方是否为对象且另一方为string,number,或者symbol,是的话就会把object转化为原始类型再进行判断
闭包
闭包的定义很简单:函数A内部有一个函数B,函数B可以访问到函数A中的变量,那么函数B就是闭包
function A(){
let a = 1
window.B = function(){
console.log(a)
}
}
A()
B()
很多人对于闭包的解释可能就是函数嵌套了函数,然后返回一个函数,其实解释是不完整的,就比如我们上面的例子就可以反驳这个观点
在js中,闭包存在的意义就是让我们可以间接访问函数内部的变量
面试题 循环中使用闭包解决var定义的问题
for(var i =1 ;i<=5;i++){
setTimeout(function(){
console.log(i)
},i*1000)
}
首先因为setTimeout是个异步函数,所以会先把循环全部执行完毕,这个时候i就是6了,所以就会输出一堆6
解决办法有三种,第一种是使用闭包的方式
for(var i=1;i<=5;i++){
(function(j){
setTimeout(function timer(){
console.log(j)
},j*1000)
})(i)
}
在上述代码中,我们首先使用了立即执行函数将i传入函数内部,这个时候值就被固定在了参数j上面不会被改变,当下执行timer闭包这个时候,就可以使用外部函数的变量j,从而达到目的。
第二种就是使用setTimeout的第三个参数,这个参数会被当成timer函数的参数传入
for(var i =1;i<=5;i++){
setTimeout(function(j){
console.log(j)
},i*1000,i)
}
第三种就是我们使用let定义i来解决问题了,这也是最为推荐的方式
for(let i=1;i<=5;i++){
setTimeout(function timer(){
console.log(i)
},i*1000)
}
深浅拷贝
面试题 深,额是浅拷贝?如何实现浅拷贝?什么是深拷贝?如何实现深拷贝?
在上一章节中,我们了解了对象类型在赋值过程中其实就是赋值了地址,从而导致改变了一方其他也都被改变的情况。通常在开发中我们不希望出现这样的问题,我们可以使用浅拷贝来解决这个问题。
let a ={
age :1
}
let b = a
console.log(b.age)
浅拷贝
首先可以通过Object.assign()来解决这个问题,很多认为这个函数是用来深拷贝的,其实不是Object.assign只会拷贝所有的属性值到新的对象中,如果属性值是对象的话,拷贝的就是地址,所以并不是深拷贝
let a = {
age: 1
}
let b = Object.assign({},a)
a.age =2
console.log(b.age)
另外我们还可以通过展开运算符 ...来实现浅拷贝
let a = {
age : 1
}
let b = {...a}
a.age = 2
console.log(b.age)
通常浅拷贝就能解决大部分问题了,但是当我们遇到如下情况就可能需要使用深拷贝了
let a = {
age :1,
jobs :{
first :'FE'
}
}
let b = {...a}
a.jobs.first ='native'
console.log(b.jobs.first)
浅拷贝值解决了第一次的问题,如果接下去的值还有对象的话,那么就回到最开始的话题了,两者享有相同的地址。要解决这个问题,我们就得使用深拷贝了。
深拷贝
这个问题通常可以通过JSON.parse(JSON.stringify(object))来解决。
let a={
age:1,
jobs:{
first:'FE'
}
}
let b JSON.parse(JSON.stringify(a))
a.jobs.first = 'native'
console.log(b.jobs.first)
但这方法也是有局限性得:
- 会忽略undefined
- 会忽略symbol
- 不能序列化函数
- 不能解决循环引用得对象
let obj = {
a: 1,
b: {
c: 2,
d: 3,
},
}
obj.c = obj.b
obj.e = obj.a
obj.b.c = obj.c
obj.b.d = obj.b
obj.b.e = obj.b.c
let newObj = JSON.parse(JSON.stringify(obj))
console.log(newObj)
如果有这么一个循环引用得对象,你会发现并不能通过该方法实现深拷贝
在遇到函数,undefiend或者symboldd额时候,该对象也不能正常得序列化
let a = {
age :undefined,
sex:Symbol('male'),
jobs:function(){},
name:'yck'
}
let b =JSON.parse(JSON.stringify(a))
console.log(b)
你会发现上述情况中,该方法忽略调了函数和undefined
但是在通常情况下,复杂数据是可以序列化得,所以这个函数可以解决大部分问题
如果你所需拷贝的对象含有内置类型并且不能包含函数,可以使用MessageChannel
function structuralclone(obj){
return new Promise(resolve =>{
const {port1,port2} = new MessageChannel()
port2.onmessage = ev =>resolve(ev.data)
port1.postMessage(obj)
)
}
var obj = {
a:1,
b:{
c:2
}
}
obj.b.d = obj.b
//注意该方法时异步的
//可以处理undefined和循环对象
const test = async () =>{
const clone = await structuralclone
console.log(clone)
}
test()
当然你可能想自己来实现一个深拷贝,但其实实现一个深拷贝是有困难的,需要我们考虑好多边界的情况,比如原型链如何处理,动漫如何处理等等,所以这里我们实现的深拷贝只是简易版
function deepClone(obj) {
function isObject(o) {
return (typeof o === 'object' || typeof o === 'function') && o !== null
}
if (!isObject(obj)) {
throw new Error('非对象')
}
let isArray = Array.isArray(obj)
let newObj = isArray ? [...obj] : { ...obj }
Reflect.ownKeys(newObj).forEach(key => {
newObj[key] = isObject(obj[key]) ? deepClone(obj[key]) : obj[key]
})
return newObj
}
let obj = {
a: [1, 2, 3],
b: {
c: 2,
d: 3
}
}
let newObj = deepClone(obj)
newObj.b.c = 1
console.log(obj.b.c) // 2
原型
面试题 如何理解原型?如何理解原型链?
当我们创建一个对象时let obj = {age:25},我们就可以发现能使用很多种函数,但是我们明明没有定义过他们,对这种情况n你是否有过疑惑?
当我们在浏览器种打印obj时你会发现,在obj上居然还有一个__proto__属性,那么看来之前的疑问就和这个属性有关系了。
其实每个js对象都有__proto__属性,这个属性指向了原型。这个属性在现在来说已经bbu推荐直接使用他了,这是浏览器在早期为了让我们访问到内部属性prototype来实现的一个东西。
讲到这里好像还是没有弄明白什么时原型,接下俩我们再看看__proto__里面有什么把

看到这里你应该明白了,原型也是一个对象,并且这个对象中包含了很多函数,所以我们可以得到一个结论:对于obj来说,可以通过__proto__找到一个原型对象,在该对象中定义了很多函数来让我们使用。
在上面的图中我们还可以发现一个connstructor属性,也就是构造函数

打开constructor属性我们又可以发现还有一个prototype属性,并且这个属性对应的值和先前我们在__proto__中看到的一模一样,所以我们可以又可以得到一个结论:原型的constructor属性指向构造函数,构造函数又通过prototype属性指向原型,但并不是所有函数都具有这个属性
Function.prototype.bind() 就没有这个属性。
其实原型就是这么简单,接下来我们再看一张图,相信这张图能让你彻底明白原型和原型链

看完这张图我们再解释一下什么是原型链。其实原型链就是多个对象通过__proto__的方式l连接了起来。为什么obj可以访问到valueof函数,就是应为obj通过原型链找到了valueof函数
- Object 是所有对象的爸爸,所有对象都可以通过 proto 找到它
- Function 是所有函数的爸爸,所有函数都可以通过 proto 找到它
- 函数的 prototype 是一个对象 对象的 proto 属性指向原型, proto 将对象和原型连接起来组成了原型链