JS基础知识点

"JS基础知识点及常考面试题(二) "

Posted by monkey-yu on January 10, 2019 | 浏览量: -

JS基础知识点及常考面试题(二) 来自掘金小册

== VS ===

Q1.== 和===的区别?

对于==来说,如果对比双方的类型不一样,就会进行类型转换。判断流程如下:

  1. 首先判断两者类型是否相同,相同的话就是比大小了;
  2. 类型不相同的话,就会进行类型转换;
  3. 会判断是否在对比null 和undefined,是的话就返回true;
  4. 判断两者类型是否为string和number,是的话将string转为number;
  5. 判断一方是否为boolean,是的话把boolean转为number再判断;
  6. 判断一方是否为object 且另一方为string、number或者symbol,是的话把objext转为原始类型再判断。

如果你觉得记忆步骤太麻烦的话,我还提供了流程图供大家使用:

对于===来说,就是判断两者的类型和值是否相同。

闭包

Q2.什么是闭包?

闭包的定义其实很简单:函数A内部有一个函数B,函数B可以访问到函数A中的变量,那么函数B就是闭包。

1
2
3
4
5
6
7
8
function A(){
    let a =1;
    window.B = function(){
        console.log(a);
    }
};
A(); 
B();     // 1

很多人对于闭包的解释可能是函数嵌套了函数,然后返回一个函数。其实这个解释是不完整的,就比如上面这个例子就反驳了这个观点。

在JS中,闭包存在的意义是让我们可以间接访问函数内部的变量

Q3.循环中使用闭包解决‘var’定义函数的问题?

1
2
3
4
5
for(var i =1;i<=5;i++){
    setTimeout(function timer(){
       console.log(i) 
    },i*1000)
}

首先因为setTimeout 是异步函数,所以会把循环全部执行完毕,这时候i 就是6了,所以会输出一堆6。

解决方法有三种:

第一种:使用闭包的方式
1
2
3
4
5
6
7
for(var i =1;i<=5;i++){
    ;(function(j){
        setTimeout(function timer(){
       		console.log(j) 
    	},j*1000)
    })(i)
}

使用立即执行函数将i传入函数内部,这个时候值就被固定在了参数j上面不会改变,当下次执行timer这个闭包是会,就可以用外部函数的变量j,从而达到目的。

第二种:使用setTimeout的第三个参数,这个参数会被当成timer函数的参数传入。
1
2
3
4
5
6
7
8
9
for (var i = 1; i <= 5; i++) {
  setTimeout(
    function timer(j) {
      console.log(j)
    },
    i * 1000,
    i
  )
}
第三种:使用let定义i(最推荐)
1
2
3
4
5
for(let i =0;i<=5;i++){
    setTimeout(function timer(){
        console.log(i)
    },i*1000)
}

深浅拷贝

Q4.什么是浅拷贝?如何实现浅拷贝?什么是深拷贝?如何实现深拷贝?

在之前,我们了解了对象类型在赋值的过程中其实是复制了地址,从而会导致改变了一方其他也都被改变的情况。通常在开发中,我们不希望出现这样的问题,我们可是使用浅拷贝来解决这个问题。

1
2
3
4
5
6
let a ={
    age:1
}
let b =a;
a.age =2;
console.log(b.age)   // 2
浅拷贝

首先可以通过Object.assign来解决这个问题,很多人以为这个函数是用来深拷贝的。其实并不是,Object.assign只会拷贝所有的属性值到新的对象中,如果属性值是对象的话,拷贝的是地址,所以并不是深拷贝。

1
2
3
4
5
6
let a={
    age:1
}
let b =Object.assign({},a);
a.age =2;
console.log(b.age)   // 1

另外我们还可以通过展开运算符…来实现浅拷贝。

1
2
3
4
5
6
let a={
    age:1
}
let b ={...a}
a.age =2;
console.log(b.age)   // 1

通常浅拷贝能解决大部分问题了,但是浅拷贝只能解决第一层的问题,如果接下去的值中还有对象的话,那么就又回到最开始的话题了,两者享有相同的地址。如下面:

1
2
3
4
5
6
7
8
9
let a={
    age:1,
    jobs:{
        first:'FE'
    }
}
let b ={...a}
a.jobs.first = 'native'
console.log(b.jobs.first)      // native

要解决这个问题,我们就得使用深拷贝了。

深拷贝

这个问题通过JSON.parse(JSON.stringify(object))来解决。

1
2
3
4
5
6
7
8
9
let a={
    age:1,
    jobs:{
        first:'FE'
    }
}
let b =JSON.parse(JSON.stringfy(a));
a.jobs.first = 'native'
console.log(b.jobs.first)          // FE

但是该方法也是有局限性的:

  • 会忽略undefined
  • 会忽略symbol
  • 不能序列化函数
  • 不能解决循环引用的对象

在遇到函数、 undefined 或者 symbol 的时候,该对象也不能正常的序列化

1
2
3
4
5
6
7
8
let a = {
  age: undefined,
  sex: Symbol('male'),
  jobs: function() {},
  name: 'zhang san'
}
let b = JSON.parse(JSON.stringify(a))
console.log(b) // {name: "zhang san"}

原型

Q5.如何理解原型?如何理解原型链?

当我们创建一个对象obj时,在浏览器中打印obj时你会发现,在obj上居然有一个_proto_ 属性。其实每个JS对象都有 _proto_属性,这个属性指向了原型。这个属性在现在来说已经不推荐直接去使用它了,这只是浏览器在早期为了让我们访问到内部属性 [[prototype]] 来实现的一个东西。

js-22

对于obj来说,可以通过_proto_找到一个原型对象,在该对象中定义了很多函数让我们来使用。在上面的图中我们还可以发现一个 constructor 属性,也就是构造函数。打开constructor属性我们又可以发现其中还有一个prototype属性,并且这个属性对应的值和先前我们在_proto_中看到的一模一样。所以:原型的constructor属性指向构造函数,构造函数又通过prototype属性指回原型,但是并不是所有函数都具有这个属性,Function.prototype.bind() 就没有这个属性。

其实原型就是那么简单,接下来我们再来看一张图,相信这张图能让你彻底明白原型和原型链。

原型链就是多个对象通过 __proto__ 的方式连接了起来。为什么 obj 可以访问到 valueOf 函数,就是因为 obj 通过原型链找到了 valueOf 函数。

总结:

  • Object 是所有对象的爸爸,所有对象都可以通过 __proto__ 找到它
  • Function 是所有函数的爸爸,所有函数都可以通过 __proto__ 找到它
  • 函数的 prototype 是一个对象
  • 对象的 __proto__ 属性指向原型, __proto__ 将对象和原型连接起来组成了原型链