1. 请简述var,let,const的区别
var:函数作用域,可以重复声明,也可以重新赋值
let:块级作用域,不可以重复声明,可以重新赋值
const:块级作用域,不可以重复声明,不可以重新赋值,声明时必须初始化
2. 解释垃圾回收机制
如果一个对象不再被程序中的任何部分引用,那么这个对象可以被回收。js自动找到那些已经不在使用的内存,把他们释放掉,从而避免内存一直被占用
3. 垃圾回收的方式
- 标记清除
- 标记整理
- 分代收集
- 引用计数
4. js现有数据类型
- Number
- String
- Boolean
- Undefined
- Null
- Symbol
- BigInt
- Object(Array,Function,Date,RegExp)
5. this指向

6. 实现数组扁平化
js
let arr = [1, [2, 3], [4, [5, 6]]];
let result = arr.flat(2); //这里的 2 表示展开两层
console.log(result);
function flatten(arr) { //递归
let result = [];
arr.forEach(item => {
if (Array.isArray(item)) {
result.push(...flatten(item));
}else {
result.push(item)
}
});
return result;
}
7. 实现数组去重
- 用set
js
let arr = [1, 2, 2, 3, 4, 4, 5];
let result = [...new Set(arr)];
console.log(result);//[1, 2, 3, 4, 5]
- 用indexOf
js
let arr = [1, 2, 2, 3, 4, 4, 5];
let result = [];
arr.forEach(function(item) {
if (result.indexOf(item) === -1) { //如果result里面没有item,就把他放回去
result.push(item);
}
});
console.log(result);
8. 讲一下JS的事件流
事件流:当一个事件发生时,事件会按照一定的路径,在DOM元素之间传播。
主要有两种事件流模型:事件冒泡和事件捕获
事件流分成三个阶段:
① 捕获阶段
② 目标阶段
③ 冒泡阶段
8.1. 事件捕获
从外往里走
事件捕获 是指当一个元素触发事件后,事件会从最外层的父元素开始,依次向内传播到真正触发事件的目标元素 ,也就是从外到内的传播过程。例如点击 button,事件会按照 document → html → body → 父元素 → button 的顺序传播。事件捕获 = 到达目标元素之前,从外到内依次经过祖先元素
js
<div>
<button>点击</button>
</div>
当点击button时
js
document
↓
html
↓
body
↓
div
↓
button
8.3. 事件冒泡
从里往外走
事件冒泡 是指当一个元素触发事件后,事件会从最里层的目标元素开始,依次向外层父元素传播 ,直到 document。例如点击 button,事件会依次从 button → 父元素 → 更外层父元素 传播。addEventListener() 默认采用事件冒泡,如果不希望事件继续向外传播,可以使用 event.stopPropagation() 阻止事件冒泡。
js
event.stopPropagation(); //阻止冒泡,阻止事件继续传播
9. 闭包
闭包 = 内部函数+内部函数创建时保存的外部词法环境
闭包 = 函数+他记住的外部作用域
闭包是指内部函数能够访问其外部函数作用域中的变量,并且当内部函数在外部继续存在或执行时,这些变量仍能被访问的现象
一句话解释为什么变量不消失:
因为存活的内部函数仍然引用着外部变量,变量仍然可达,所以垃圾回收器不会回收它。
经典用途:计数器
js
function counter() {
let num = 0;
return function() {
num++;
console.log(num);
};
}
let fn = counter();
fn(); // 1
fn(); // 2
fn(); // 3
10.事件循环
js是单线程的,同一时间只能执行一个任务
调用栈:js正在执行哪些函数
js
console.log(1); //执行,调用栈(先进后出),输出1后,执行完,出栈
setTimeout(() => {
console.log(2);
}, 1000); // 异步操作,js不执行,先跳过执行后面的
console.log(3); // 执行,执行完成后,调用栈空了。
// 定时器回调,进入任务队列(先进先出)。当调用栈为空时,把回调拿到调用栈执行
// 1
// 3
// 2
一个宏任务执行结束以后,要先把微任务队列清空,然后才能执行下一个宏任务。
js
console.log(1);
setTimeout(() => {
console.log(2);
Promise.resolve().then(() => {
console.log(3);
});
}, 0);
Promise.resolve().then(() => {
console.log(4);
setTimeout(() => {
console.log(5);
}, 0);
});
console.log(6);
js
// 最终答案
1
6
4
2
3
5
11. 继承和委托
让一个对象能够访问另一个对象的属性和方法
一个对象可以获得另一个对象已有的属性和方法,同时还可以拥有自己独有的属性和方法
js
const person = {
name: '张三',
eat() {
console.log('吃饭');
}
};
const student = Object.create(person);
student.study = function() {
console.log('学习');
};
console.log(student.name);
student.eat();
student.study();
对象没有自己某个属性和方法时,把查找工作委托给它的原型
JS 的继承表面上可以用 class -- extends-- 来写,但底层核心仍然是原型链;对象自己找不到属性/方法时,就沿原型链向上查找,这个过程就是委托。
12. 回调函数、箭头函数、构造函数
12.1 箭头函数
一个参数可以省略括号
js
const fn = (x) => {
return x * 2;
};
可以省略括号:
js
const fn = x => {
return x * 2;
};
只有一条 return,可以省略return
js
const fn = (x) => {
return x * 2;
};
可以省略return
js
const fn = x => x * 2;
12.2 构造函数
js
function Person(name, age) {
this.name = name;
this.age = age;
}
let p1 = new Person("张三", 18);
箭头函数不能作为构造函数