1. typeof 与 instanceof 区别

typeof会返回一个变量的基本类型,instanceof返回的是一个布尔值

instanceof` 可以准确地判断复杂引用数据类型,但是不能正确判断基础数据类型

2. let var const 区别

1.var 声明的变量是存在变量提升的,let ,const声明的变量不存在变量提升。

变量提升:变量提升是指代码在未定义之前可以使用且不会报错

2.在同一作用域下var可以重复声明,let,const不可以重复声明。

3.var声明的变量不存在块级作用域, let,const声明的变量存在块级作用域。

4.var和let可以重新赋值 ,const声明的是一个常量,const声明的变量必须要进行初始化 不能够重新赋值 。

5.var声明的变量不存在暂时性死区,let,const声明的变量存在暂时性死区。

暂时性死区:在变量声明之前,任何的地方都不能去提前使用,一旦使用就会报错,那么这之前的这些代码,对于这个变量来说就是暂时性死区

3. 对作用域的理解

作用域,即变量(变量作用域又称上下文)和函数生效(能被访问)的区域或集合。换句话说,作用域决定了代码区块中变量和其他资源的可见性

比如:

1
2
3
4
5
function myFunction() {
let a = "函数内部变量";
}
myFunction();//要先执行这个函数,否则根本不知道里面是啥
console.log(inVariable);

上述例子中,函数 myFunction 内部创建一个 a 变量,当我们在全局访问这个变量的时候,系统会报错。说明我们在全局是无法获取到(闭包除外)函数内部的变量

从变量查找的范围的角度,js 的作用域可以分为3类:全局作用域、函数作用域和块级作用域。

3.1 全局作用域

任何不在函数中或是大括号中声明的变量,都是在全局作用域下,全局作用域下声明的变量可以在程序的任意位置访问

1
2
3
4
5
var a = 'Hello World!';
function my() {
console.log(a);
}
my();// 打印 'Hello World!'

3.2 局部作用域

局部作用域也叫函数作用域,如果一个变量是在函数内部声明的它就在一个函数作用域下面。这些变量只能在函数内部访问,不能在函数以外去访问

1
2
3
4
5
6
function my() {
var a = 'Hello World!';
console.log(a);
}
greet();// 打印 'Hello World!'
console.log(a);// 报错

3.3 块级作用域

ES6引入了letconst关键字,和var关键字不同,在大括号中使用letconst声明的变量存在于块级作用域中。在大括号之外不能访问这些变量

1
2
3
4
5
6
7
8
{
// 块级作用域中的变量
let a = 'Hello World!';
var b = 'English';
console.log(a); // 'Hello World!'
}
console.log(b);// 变量 'English'
console.log(a);// 报错:

3.4 词法作用域

词法作用域,又叫静态作用域,变量被创建时就确定好了,而非执行阶段确定的。也就是说我们写好代码时它的作用域就确定了,JavaScript 遵循的就是词法作用域

1
2
3
4
5
6
7
8
9
var a = 2;
function foo(){
console.log(a)
}
function bar(){
var a = 3;
foo();
}
n() //2

因为 JavaScript 遵循词法作用域,相同层级的 foo 和 bar 就没有办法访问到彼此块作用域中的变量,所以输出2

3.5 作用域链

当在 JavaScript 中使用一个变量的时候,首先Javascript引擎会尝试在当前作用域下去寻找该变量,如果没找到,再到它的上层作用域寻找,以此类推直到找到该变量或是已经到了全局作用域。

如果在全局作用域里仍然找不到该变量,它就会在全局范围内隐式声明该变量(非严格模式下)或是直接报错

1
2
3
4
5
6
7
8
9
10
11
12
var sex = '男';
function person() {
var name = '张三';
function student() {
var age = 18;
console.log(name); // 张三
console.log(sex); // 男
}
student();
console.log(age); // Uncaught ReferenceError: age is not defined
}
person();

上述代码主要主要做了以下工作:

1.student函数内部属于最内层作用域,找不到name,向上一层作用域 person 函数内部找,找到了输出“张三”

2.student内部输出cat时找不到,向上一层作用域 person 函数找,还找不到继续向上一层找,即全局作用域,找到了输出“男”

3.在person函数内部输出age时找不到,向上一层作用域找,即全局作用域,还是找不到则报错

4. 原型和原型链

原型:函数有原型,函数上有个属性叫 prototype ,函数的这个原型指向一个对象 ,也就是原型对象 ,这个原型对象有一个 constructor 属性 指向函数本身

1
2
function a(){}
console.log( a.prototype );

控制台的输出:

1
2
3
4
5
6
7
8
9
10
11
12
{
constructor: ƒ a(),
__proto__: {
constructor: ƒ Object(),
hasOwnProperty: ƒ hasOwnProperty(),
isPrototypeOf: ƒ isPrototypeOf(),
propertyIsEnumerable: ƒ propertyIsEnumerable(),
toLocaleString: ƒ toLocaleString(),
toString: ƒ toString(),
valueOf: ƒ valueOf()
}
}

原型链:每一个实例对象上都有一个proto属性,指向构造函数的原型对象,构造函数的原型对象也是一个对象;也有一个proto属性,这样一层一层往上找到过程就形成原型链

当我们访问对象中的属性时候,会先访问该对象中的本身的属性(私有属性),如果访问不到,会查找对象的“proto”指向的构造函数的prototype对象,如果其中有要访问的属性,就使用该值,否则继续访问prototype的“proto”,在其中查找要访问属性。这样一直上溯到Object对象。这个就是“原型链”。

5. 防抖与节流

本质上是优化高频率执行代码的一种手段

如:浏览器的 resizescrollkeypressmousemove 等事件在触发时,会不断地调用绑定在事件上的回调函数,极大地浪费资源,降低前端性能

为了优化体验,需要对这类事件进行调用次数的限制,对此我们就可以采用throttle(防抖)和debounce(节流)的方式来减少调用频率

5.1 防抖与节流的定义

节流:当持续触发事件时,保证在一定时间内只调用一次事件处理函数,意思就是说,假设一个用户一直触发这个函数,且每次触发小于既定值,函数节流会每隔这个时间调用一次

防抖:当持续触发事件时,一定时间段内没有再触发事件,事件处理函数才会执行一次,如果设定时间到来之前,又触发了事件,就重新开始延时。也就是说当一个用户一直触发这个函数,且每次触发函数的间隔小于既定时间,那么防抖的情况下只会执行一次。

用一句话总结防抖和节流的区别:防抖是将多次执行变为最后一次执行,节流是将多次执行变为每隔一段时间执行

5.2 节流

场景:点击按钮重复发送请求:

项目中点击创建、编辑、删除等按钮都会发送http请求,网络卡顿的情况下点击按钮之后不能快速的响应,一般情况下用户会重复点击按钮,所以会造成重复发送请求问题,一定量造成卡顿延迟问题,这个时候便可以采用节流

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var throttle = function(func, delay) {
var timer = null; //定义一个计时器
return function() {
var context = this; //this指向window
var args = arguments; //
if (!timer) {
timer = setTimeout(function() {
func.apply(context, args);
timer = null;
}, delay);
}
}
}
function handle() {
console.log(Math.random());
}
window.addEventListener('scroll', throttle(handle, 1000));

5.3 防抖

场景:input事件模糊查询

input输入框的input事件会在输入框内容发生改变的时候执行,那么就存在一个问题:每次输入,都会触发input事件,执行函数,或者接口请求,而这并不是我们想要的:比如,你想要模糊查询 “liu” 相关的所有数据,而当你在input框中输入 “l” 的时候就已经触发了input事件,去请求了接口,而这并不是我们想要的。所以,我们的防抖函数就登场了;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function debounce(fn, wait) {
var timeout = null; //定义一个定时器
return function() {
if(timeout !== null)
clearTimeout(timeout); //清除这个定时器
timeout = setTimeout(fn, wait);
}
}
// 处理函数
function handle() {
console.log(Math.random());
}
// 滚动事件
window.addEventListener('scroll', debounce(handle, 1000));

6. JS 数组去重

Set去重:

1
2
3
var arr = [1,2,2,4,3,4,1,3,2,7,5,6,1]
var newArr = new Set(arr)
console.log(newArr)

indexOf去重:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 数组去重 
var arr = [1,2,2,4,3,4,1,3,2,7,5,6,1]
function fn(arr){
let newArr = [] ;
for(let i = 0;i<arr.length;i++){
//判断新数组是否存在当前元素arr[i]
if(newArr.indexOf(arr[i]) === -1){
//元素不存在
newArr.push(arr[i])
}
}
return newArr;
}
console.log(fn(arr))

7. this指向问题

1.全局作用域或者普通函数中 this 指向全局对象 window。

1
2
3
4
5
6
7
8
9
10
//直接打印
console.log(this) //window

//function声明函数
function a() {console.log(this)}
a() //window

//function声明函数赋给变量
var bar = function () {console.log(this)}
bar() //window

2. 方法调用中谁调用 this 指向谁

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//对象方法调用
var a = {
run: function () {console.log(this)}
}
a.run() // a

//事件绑定
var btn = document.querySelector("button")
btn.onclick = function () {
console.log(this) // btn
}
//事件监听
var btn = document.querySelector("button")
btn.addEventListener('click', function () {
console.log(this) //btn
})

3. 在构造函数或者构造函数原型对象中 this 指向构造函数的实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//不使用new指向window
function a(name) {
console.log(this) // window
this.name = name;
}
a('111')
//使用new
function a(name) {
this.name = name
console.log(this) //people
self = this
}
//这里new改变了this指向,将this由window指向Person的实例对象people
var people = new Person('iwen')
console.log(self === people) //true

4. 箭头函数中指向外层作用域的 this

1
2
3
4
5
6
7
8
9
10
11
var obj = {
foo() {
console.log(this);
},
bar: () => {
console.log(this);
}
}

obj.foo() // {foo: ƒ, bar: ƒ}
obj.bar() // window

8.箭头函数和普通函数有什么区别

区别:

箭头函数在一些情况下书写更简洁(如只有一个参数、函数体直接返回值时候)。

箭头函数没有自己的this,箭头函数内的this变量指向外层非箭头函数的函数的this,或者将该箭头函数作为属性的对象。箭头函数也不支持call()和apply()函数特性。

箭头函数内部不可以使用arguments对象。

箭头函数不可以当做构造函数。