JavaScript考核详解
文章目录
- JavaScript考核详解
- 一、请简述var,let,const的区别?
- 二、解释垃圾回收机制,垃圾回收的方式?
- 三、以下代码的输出是什么?
- 四、this的指向
- 五、实现数组扁平化
- 六、实现数组去重
- 七、JS中的基本类型
JavaScript考核详解
一、请简述var,let,const的区别?
- 块级作用域:块级作用域有大括号
{ }
包括,let和const具有块级作用域,var不存在块级作用域。块级作用域解决了ES6中的两个问题:- 内层变量可能覆盖外层变量
- 用来计数的循环变量泄露为全局变量
- 变量提升:var存在变量提升,let和const不存在变量提升,即变量只能在声明之后使用,否则会报错。
- 给全局添加属性:浏览器的全局对象是window,Node的全局对象是global。var声明的变量为全局变量,并且会将该变量添加为全局对象的属性,但是let和const不会。
- 重复声明:var声明变量时,可以重复声明变量,后声明的同名变量会覆盖之前声明的变量。const和let不允许重复声明变量。
- 暂时性死区:在使用let、const命令声明变量之前,该变量都是不可用的。这在语法上,称为暂时性死区。使用var声明的变量不存在暂时性死区,因为var声明的变量都会被提升到作用域的最顶部。
- 初始值设置:在变量声明时,var 和 let 可以不用设置初始值。而const声明变量必须设置初始值。
- 指针指向:let和const都是ES6新增的用于创建变量的语法。 let创建的变量是可以更改指针指向(可以重新赋值)。但const声明的变量是不允许改变指针的指向。
二、解释垃圾回收机制,垃圾回收的方式?
-
垃圾回收的概念:JavaScript代码运行时,需要分配内存空间来储存变量和值。当变量不在参与运行时,就需要系统收回被占用的内存空间,这就是垃圾回收。
回收机制:
-
Javascript 具有自动垃圾回收机制,会定期对那些不再使用的变量、对象所占用的内存进行释放,原理就是找到不再使用的变量,然后释放掉其占用的内存。
-
JavaScript中存在两种变量:局部变量和全局变量。全局变量的生命周期会持续到页面卸载;而局部变量声明在函数中,它的生命周期从函数执行开始,直到函数执行结束,在这个过程中,局部变量会在堆或栈中存储它们的值,当函数执行结束后,这些局部变量不再被使用,它们所占有的空间就会被释放。
-
不过,当局部变量被外部函数使用时,其中一种情况就是闭包,在函数执行结束后,函数外部的变量依然指向函数内部的局部变量,此时局部变量依然在被使用,所以不会回收。
function outer(){let i = 1function fn(){console.log(i)}return fn } const fun = outer() fun() // 返回1 // 外层函数使用内部函数的变量
-
-
垃圾回收的方式:
-
引用计数法:IE采用的引用计数算法,定义“内存不再使用”,就是看一个对象是否有指向它的引用,没用引用了就回收对象。
算法:
- 跟踪记录被引用的次数
- 如果被引用类一次,那么就记录次数加一,多次引用会累加
- 如果减少一个引用就减一
- 如果引用次数是0,则释放内存
但引用计数法存在一个致命的问题:嵌套引用(循环引用)。
如果两个对象相互引用,尽管他们已不再使用,垃圾回收器不会进行回收,导致内存泄露,因为他们的引用次数永远不会是0,这样的相互引用如果大量存在会导致大量的内存泄露。
-
标记清除法:现代的浏览器已经不再使用引用计数算法,使用的大多是基于标记清除算法的某些改进算法,总体思想都是一致的。
核心:
- 标记清除算法将“不再使用的对象”定义为“无法达到的对象”。
- 就是从根部(在JS中就是全局对象)出发定时扫描内存中的对象。凡是能从根部到达的对象,都是还需要使用的。
- 那些无法由根部出发的对象被标记为不再使用,稍后进行回收。
-
三、以下代码的输出是什么?
var tmp = new Date();function fn(){console.log(tmp);if(false){var tmp = 'hello world';}
}fn();
答案:undefined。
解释:var存在变量提升,var声明的变量会被提升到作用域的顶层,但该声明不包括赋值,所以虽然if判断里的语句虽然并不会执行,但其实已经在fn函数作用域内的顶部声明了一个未被赋值的tmp变量,所以console.log(tmp)
打印出来的结果是undefined。
四、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();
后this指向window。
原因:当函数被作为普通函数调用时,this指向全局对象(在浏览器中是
window
,在Node.js中是global
),或者在严格模式('use strict'
)下,this
是undefined
。
调用person.sayName();
后this指向person。
原因:当函数被作为对象的方法调用时,
this
指向该对象。
调用(person.sayName)();
后this指向person。
原因:当函数被作为对象的方法调用时,
this
指向该对象。
调用(b = person.sayName)();
后this指向window。
原因:当函数被作为普通函数调用时,this指向全局对象(在浏览器中是
window
,在Node.js中是global
),或者在严格模式('use strict'
)下,this
是undefined
。
调用后person.hello()
this指向window
原因:箭头函数不绑定自己的
this
,它会捕获其所在上下文的this
值,作为自己的this
值。
五、实现数组扁平化
JavaScript中实现数组扁平化(即将多维数组转换为一维数组)有多种方法,以下是一些常见的方法:
- 使用递归
递归是处理嵌套结构如多维数组的一个直观方法。你可以编写一个函数,该函数检查数组中的每个元素,如果元素是数组,则递归调用该函数;否则,将元素添加到结果数组中。
function flattenArray(arr) {let result = [];for (let i = 0; i < arr.length; i++) {if (Array.isArray(arr[i])) {result = result.concat(flattenArray(arr[i]));} else {result.push(arr[i]);}}return result;
}console.log(flattenArray([1, [2, [3, [4]], 5]])); // 输出: [1, 2, 3, 4, 5]
- 使用栈
迭代方法结合栈也是处理数组扁平化的有效方式。我们可以将数组元素逐一压入栈中,并在遇到数组时将其元素再次压入栈中,直到栈为空。
function flattenArrayWithStack(arr) {let stack = [...arr];let res = [];while (stack.length) {// 取出栈的最后一个元素const next = stack.pop();if (Array.isArray(next)) {// 如果是数组,则将数组元素逆序压入栈中// 这样可以保证原始数组的顺序在结果中被保留stack.push(...next.reverse());} else {// 否则,将元素添加到结果数组中res.push(next);}}// 因为我们是从栈中取元素,所以结果数组是逆序的,需要反转return res.reverse();
}console.log(flattenArrayWithStack([1, [2, [3, [4]], 5]])); // 输出: [1, 2, 3, 4, 5]
- 使用ES6的扩展运算符和
reduce
这种方法利用了ES6的扩展运算符...
和数组的reduce
方法,通过递归的方式实现扁平化。
function flattenArrayWithReduce(arr) {return arr.reduce((acc, val) => Array.isArray(val) ? [...acc, ...flattenArrayWithReduce(val)] : [...acc, val], []);
}console.log(flattenArrayWithReduce([1, [2, [3, [4]], 5]])); // 输出: [1, 2, 3, 4, 5]
- 使用
flat()
(ES2019引入)
从ES2019开始,JavaScript引入了一个名为flat()
的数组方法,它可以用来实现数组的扁平化,其默认只会扁平化一层,但你可以通过传递一个整数参数来指定扁平化的深度,如果深度为Infinity
,则表示无论嵌套多少层都将被扁平化。
const arr = [1, [2, [3, [4]], 5]];console.log(arr.flat(Infinity)); // 输出: [1, 2, 3, 4, 5]
这是最简单也最现代的方法,但请注意,它可能不适用于需要兼容较旧JavaScript环境的场景。
六、实现数组去重
题目:给定某无序数组,要求去除数组中的重复数字并且返回新的无重复数组。
解答:
- 使用ES6的
Set
数据结构
Set
是一个构造函数,它创建一个值的集合,其中的值都是唯一的,没有重复的值。因此,将数组转换为Set
,然后再转换回数组,可以实现去重。
function uniqueArray(arr) {return [...new Set(arr)];
}console.log(uniqueArray([1, 2, 2, 3, 4, 4, 5])); // 输出: [1, 2, 3, 4, 5]
- 使用
filter()
和indexOf()
遍历数组,对于当前元素,使用indexOf()
检查它首次出现的位置是否和当前位置相同,如果相同,则说明不是重复元素,将其保留。
function uniqueArray(arr) {return arr.filter((item, index) => arr.indexOf(item) === index);
}console.log(uniqueArray([1, 2, 2, 3, 4, 4, 5])); // 输出: [1, 2, 3, 4, 5]
但需要注意的是,这种方法在数组元素为对象时可能不适用,因为对象比较是基于引用的。
- 使用
Map
数据结构
类似于Set
,Map
也是一个集合,但它保存键值对。我们可以利用它的键是唯一的特性来实现去重。
function uniqueArray(arr) {const map = new Map();arr.forEach(item => map.set(item, true));return Array.from(map.keys());
}console.log(uniqueArray([1, 2, 2, 3, 4, 4, 5])); // 输出: [1, 2, 3, 4, 5]
- 使用
reduce()
reduce()
方法可以对数组中的每个元素执行一个由您提供的reducer函数(升序执行),将其结果汇总为单个返回值。我们可以利用这一点来构建一个新的数组,只包含不重复的元素。
function uniqueArray(arr) {return arr.reduce((acc, current) => {if (acc.indexOf(current) === -1) {acc.push(current);}return acc;}, []);
}console.log(uniqueArray([1, 2, 2, 3, 4, 4, 5])); // 输出: [1, 2, 3, 4, 5]
七、JS中的基本类型
Number、Object、BigInt、Symbol、String、Boolean、Undefined、Null
- 栈:原始数据类型(Undefined、Null、Boolean、Number、String)
- 堆:引用数据类型(对象、数组和函数)
ES6新增:Symbol、Bigint