目录
1. 请简述var, let, const的区别?
答:
- var:具有变量提升,可在变量声明前就使用该变量
- let:不具有变量提升,用let声明的变量可以重新赋值
- const:不具有变量提升,用const声明的变量:简单数据类型不能重新赋值,引用数据类型可以重新赋值,但不能更改其地址
2.解释垃圾回收机制,垃圾回收的方式?
答:
- 垃圾回收机制:当某段内存不再使用时,系统会自动回收并释放该内存,引用数据类型可由程序员手动回收
- 内存管理:垃圾回收器自动跟踪分配给对象的内存,并在对象不再被需要时释放这些内存。
- 引用:对象通过引用被其他部分的代码所访问。只要对象还有引用指向它,它就被认为是可达的,因而不应被回收。
- 垃圾:如果没有任何引用指向某个对象,那么这个对象就变成了垃圾,可以被回收
- 垃圾回收的方式:
- 标记-清除(Mark and Sweep):
标记阶段:垃圾回收器从一系列称为“根”的对象开始(比如全局对象、栈中的本地变量等),递归地遍历所有从根可达的对象,并标记这些对象。
清除阶段:未被标记的对象被认为是垃圾,垃圾回收器将释放这些未标记的对象占用的内存。 - 复制(Copying):
JavaScript引擎将可用内存划分为大小相等的两块区域。运行时只使用其中一个区域来分配内存。垃圾回收时,将正在使用的对象复制到另一个区域,并清理当前使用的区域。 - 分代收集(Generational Collection):
这种方法基于观察:大多数对象很快就会变得不可达。新创建的对象被称为“年轻代”,而存活了一段时间的对象则被移动到“老年代”。
对年轻代进行频繁的垃圾回收,而对老年代则较少回收。这样可以优化性能,因为年轻代中的大多数对象通常都是垃圾。 - 增量标记(Incremental Marking):
这种方法允许垃圾回收工作分散在整个执行过程中,而不是一次性停止所有工作来进行垃圾回收。这样可以减少长时间的暂停,提高用户体验。
- 标记-清除(Mark and Sweep):
3.以下代码的输出是什么
var tmp = new Date();
function fn(){
console.log(tmp);
if(false){
var tmp = 'hello world';
}
}
fn();
答:
undefined,用var生命的变量tmp具有变量提升的特性,因此会先使用再声明
4.this的指向
var name = "window";
var person = {
name: "person",
sayName: function () {
console.log(this.name);
},
hello: () => console.log(this.name)
};
function sayName() {
var sss = person.sayName;
sss();
person.sayName();
(person.sayName)();
(b = person.sayName)();
person.hello()
}
sayName();
答:
- sss();
sss是一个函数引用,这里直接调用它,没有显式绑定上下文。this指向全局对象window - person.sayName();
这里直接通过对象person来调用方法sayName,因此this将指向调用它的对象person - (person.sayName)();
这与第2点相同,this指向person对象,输出:“person”。 - (b = person.sayName)();
这里b只是一个指向person.sayName的引用,当调用b()时,它没有明确的上下文绑定,因此this指向全局对象window - person.hello();
hello是一个箭头函数,箭头函数不绑定自己的this值,而是从定义它的上下文中继承this值。在这个例子中,hello是在person对象的构造上下文中定义的,但是由于它是箭头函数,它会捕获外层的作用域中的this值。这里的外层作用域是指sayName函数的作用域,而不是person对象。因此,this实际上指的是全局对象window
补充知识点:箭头函数的this指向规则:
箭头函数不会创建自己的this上下文,也就是说,箭头函数内部的this值是定义时所在的上下文(lexical this),而不是调用时的上下文。这意味着箭头函数内部的this值是在定义的时候绑定的,而不是在运行时动态决定的。
5. 实现数组的扁平化
let arr = [1, [2, [3, 4, 5]]];
答:
- 使用
flat
方法
let arr = [1, [2, [3, 4, 5]]];
let flatArr = arr.flat(Infinity); // 或者指定嵌套层数,如 arr.flat(2);
console.log(flatArr); // 输出:[1, 2, 3, 4, 5]
- 递归
function flattenArray(arr) {
return arr.reduce((acc, val) =>
Array.isArray(val) ? acc.concat(flattenArray(val)) : acc.concat(val), []);
}
let arr = [1, [2, [3, 4, 5]]];
let flatArr = flattenArray(arr);
console.log(flatArr); // 输出:[1, 2, 3, 4, 5]
6. 实现数组去重
const array = [1, 2, 3, 5, 1, 5, 9, 1, 2, 8];
答:
- 使用set
const array = [1, 2, 3, 5, 1, 5, 9, 1, 2, 8];
const uniqueArray = [...new Set(array)];
console.log(uniqueArray); // 输出:[1, 2, 3, 5, 9, 8]
- 使用filter
const array = [1, 2, 3, 5, 1, 5, 9, 1, 2, 8];
const uniqueArray = array.filter((value, index, self) => self.indexOf(value) === index);
console.log(uniqueArray); // 输出:[1, 2, 3, 5, 9, 8]
- 使用Map
const array = [1, 2, 3, 5, 1, 5, 9, 1, 2, 8];
const uniqueArray = array.filter((value) => {
if (!seen.has(value)) {
seen.set(value, true);
return true;
}
return false;
}, new Map());
console.log(uniqueArray); // 输出:[1, 2, 3, 5, 9, 8]
7.JS中的基本类型
答:
- number数字型
- string字符串型
- boolean布尔型
- undefined未定义型
- null空类型
- BigInt:表示大于2^53-1的整数
- Symbol符号:是一种唯一的、不可变的数据类型,用于生成唯一的标识符,通常用作对象属性的键,避免命名冲突
8.讲一下JS的事件流
答:
事件流指的是事件如何在DOM树中传播的过程。当用户与网页交互(例如点击按钮、滚动页面等)时,会产生事件。事件流包括三个阶段:事件捕获、处于目标阶段、事件冒泡。
-
事件捕获
这是事件流的第一个阶段。在这个阶段,事件从最顶层的节点(通常是document或window)开始向下传播,直到达到目标节点。在这个阶段,可以预先拦截事件。 -
处于目标阶段
这是事件流的第二个阶段,事件到达目标节点,触发目标节点上的事件处理程序。这是事件流中最常见的阶段,也是我们通常编写事件处理程序的地方。 -
事件冒泡
这是事件流的最后一个阶段。在这个阶段,事件从目标节点开始向上冒泡,经过DOM树中的每一个父节点,直到文档的根节点。这个阶段可以用来捕获在子节点上发生的事件,并在父节点上执行某些操作。