2021最新面试题,从js基础到js高阶 从容回答
1. JavaScript垃圾回收机制的了解
在JavaScript中的字符串,对象,数组是没有固定大小的,只有当对他们进行动态分配存储时,解释器就会分配内存来存储这些数据,当JavaScript的解释器消耗完系统中所有可用的内存时,就会造成系统崩溃。
内存泄漏,在某些情况下,不再使用到的变量所占用内存没有及时释放,导致程序运行中,内存越占越大,极端情况下可以导致系统崩溃,服务器宕机。
so,JavaScript有自己的一套垃圾回收机制,JavaScript的解释器可以检测到什么时候程序不再使用这个对象了(数据),就会把它所占用的内存释放掉。
针对JavaScript的来及回收机制有以下两种方法(常用):标记清除,引用计数。
标记清除
当变量进入到执行环境时,垃圾回收器就会将其标记为“进入环境”,当变量离开环境时,就会将其标记为“离开环境”。
垃圾回收器在运行时会给存储在内存中的所有变量都加上标记,接着去掉环境环境中的变量,和被环境中的变量所引用的变量的标记,在此之后再被加上标记的变量将被视为准备删除的变量,就是要删除的变量,垃圾收集器完成内存清除工作,销毁这些带有的标记的值,回收它们所占用的内存空间。
引用计数
说到引用计数,部分人是不知道是啥的,引用计数作为垃圾回收策略的一种,含义是跟踪记录每个值被引用的次数。
当声明了一个变量并将一个引用类型赋值给该变量时,则这个值的引用次数就是为1。
相反的,如果该变量的值变成了另外一个,则这个值的引用次数减一。(当这个值的引用次数变为0的时候,说明没有变量在使用,则这个值没法被访问。)—因而就可以将它占用的空间回收起来,这样垃圾回收器就会在运行的时候清理引用次数为0的值占用的空间。
但是引用计数存在如果相互引用大量的存在会导致大量的内存泄漏;同时如果出现循环引用问题也会导致内存泄漏的问题。
所以,要减少JavaScript中的垃圾回收,在初始化的时候新建对象,然后在后续过程中尽量多的重用这些创建好的对象。我们可以:1. 数组array优化;2. 对象尽量优化;3. 循环优化。
如下内存分配方式:
{} 创建一个新对象
[] 创建一个新数组
funtion(){...} 创建一个新的方法
new Foo() new 关键字,一次内存的分配
重复利用:对对象object的优化,遍历此对象的所有属性,逐个删除属性,最终将对象清空为一个空对象。
2. 说说有几种类型的DOM节点
嗯,好的,DOM节点类型有:Document节点,整个文档是一个文档节点;Element节点,每个HTML标签是一个元素节点;Attribute节点,每一个HTML属性是一个属性节点;Text节点,包含在HTML元素中的文本是文本节点。
3.在script标签中defer和async属性的区别
一般情况下,脚本的下载和执行将会按照文档的先后顺序同步执行,当 脚本下载和执行 的时候,文档解析会被阻塞,在 脚本下载和执行 完成之后文档才会往下继续进行解析。
如果script标签中没有defer或async属性,浏览器在渲染过程中遇到script标签时,会停止渲染来下载执行js代码,等待js执行完毕后,浏览器再从中断的地方恢复渲染。
你会知道这样浏览器会造成阻塞,如果你想要你的项目首屏渲染很快的话,就尽量不要在首屏加载js文件,所以学习的时候会建议将script标签放在body标签底部。
说到defer(延迟执行)和async(异步加载)属性的区别,下面展示使用script标签有以下三种情况:
<script src="dadaqianduan.js"></script> // 浏览器会立即加载并执行相应的脚本
<script async src="dadaqianduan.js"></script> // 后续文档的加载和渲染与js脚本的加载和执行是并行进行的
<script defer src="dadaqianduan.js"></script> // 加载后续文档的过程和js脚本的加载是并行进行的,js脚本的执行需要等到文档所有元素解析完成之后,DOMContentLoaded事件触发执行之前
当加载的js脚本有多个的时候,async是无顺序的加载,而defer是有顺序的加载,defer属性表示延迟执行引入的JavaScript,这段JavaScript加载时HTML并未停止解析,so,defer是不会阻塞html解析的,它是等Dom加载完后再去执行JavaScript代码的。(当html解析过程中,遇到defer属性,就会异步加载该js文件,不会中断HTML文档的解析,当整个HTML解析完成后,回头再来解析该js文件)
- 当有defer属性时,脚本的加载过程 和 文档加载 是 异步发生的,等到 文档解析 完脚本才开始执行。
- 当有async属性时,脚本的加载过程 和 文档加载 也是异步发生的,这里注意的是 脚本下载完成后,会停止HTML解析,先执行脚本,脚本解析完 后继续HTML解析。
- 同时有async和defer属性时,执行效果与async一致。
defer属性-是否延迟执行脚本,直到页面加载为止;async属性-脚本一旦可用,就异步执行。defer属性并行加载JavaScript文件,会按照页面上的script标签顺序执行,而async并行加载,下载完成就立即执行,不会按照页面上的顺序执行。
4. 说说你对闭包的了解
面试前端,当面试官问你,谈谈你对闭包的理解的时候,该怎么回答呢?
简单说就是 定义在一个函数内部的函数,内部函数持有 外部函数 内的变量 或 参数的引用。内部函数依赖外部函数, 外部函数参数和变量 不会被垃圾回收机制回收,这些变量会始终存在于内存中。
好处可以读取函数内部的变量,可以避免全局变量的污染,坏处会增加内存的使用量,容易导致内存泄漏,解决方法就是退出函数前,将不适用的局部变量全部删除。在JavaScript中,函数即是闭包,只有函数才会产生作用域。
闭包特性,函数嵌套函数,在函数内部可以引用外部的参数和变量,参数和变量不会被垃圾回收机制回收。
由于在js中,变量的作用域属于函数作用域,在函数执行后,作用域就会被清理,内存也会被回收,但是由于闭包是建立在一个函数内部的 子函数,由于子函数可以访问上级作用域的原因,即使上级函数执行完,作用域也不会随之销毁。
在本质上,闭包就是将函数内部和函数外部连接起来的一座桥梁。
代码闭包表现形式:
// 作为函数参数传递
var a = 1;
function foo() {
var a = 2;
function dada() {
console.log(a);
}
da(dada);
}
function da(fn) {
// 闭包
fn();
}
foo(); // 输出2
5. 解释一下unshift()的方法
unshift()方法可以想数组开头添加一个或多个元素,并返回新的长度。
arrayObject.unshift(newelement1,newelement2,....,newelementX)
newelement1 必需。向数组添加的第一个元素。
newelement2 可选。向数组添加的第二个元素。
newelementX 可选。可添加若干个元素。
返回值- arrayObject
的新长度。
unshift()
方法将把它的参数插入 arrayObject
的头部,并将已经存在的元素顺次地移到较高的下标处,以便留出空间。该方法的第一个参数将成为数组的新元素 0,如果还有第二个参数,它将成为新的元素 1,以此类推。
请注意,unshift()
方法不创建新的创建,而是直接修改原有的数组。该方法会改变数组的长度。
6. 说说encodeURI()和decodeURI()的作用是什么
1.encodeURl( )
用于将URL转换为十六进制编码。2.decodeURI( )
用于将编码的URL转换回正常URL。
7. 为什么不建议在JavaScript中使用innerHTML
innerHTML
内容每次刷新,因此很慢。在innerHTML
中没有验证的余地,因此,更容易在文档中插入错误代码,从而使网页不稳定。
8. 在DOM操作中怎样创建,添加,移除,替换,插入,查找节点
DOM节点操作方法:
- 访问、获取节点
document.getElementById(id);
// 返回对拥有指定id的第一个对象进行访问
document.getElementsByName(name);
// 返回带有指定名称的节点集合
document.getElementsByTagName(tagName);
// 返回带有指定标签名的对象集合
document.getElementsByClassName(className);
// 返回带有指定class名称的对象集合
- 创建节点/属性
createDocumentFragment() //创建一个DOM片段
document.createElement(eName); // 创建一个节点
document.createAttribute(attrName); // 对某个节点创建属性
document.createTextNode(text); // 创建文本节点
- 添加节点
document.insertBefore(newNode, referenceNode); // 在某个节点前插入节点
parentNode.appendChild(newNode); // 给某个节点添加子节点
- 复制节点
cloneNode(true | false); // 复制某个节点
- 删除节点
parentNode.removeChild(node); // 删除某个节点的子节点node是要删除的节点
- 属性操作
getAttribute(name) // 通过属性名称获取某个节点属性的值
setAttribute(name,value); // 通过某个节点属性的值
removeAttribute(name); // 删除某个属性
- 获取相邻的节点
curtNode.previousSibling; // 获取已知节点的相邻的上一个节点
curtNode.nextSibling; // 获取已知节点的下一个节点
9. 如何实现浏览器内多个标签页之间的通信
使用localStorage
,使用localStorage.setItem(key,value)
;添加内容
使用storage事件监听添加、修改、删除的动作
window.addEventListener("storage",function(event){
$("#name").val(event.key+"="+event.newValue);});
$(function(){
$("#btn").click(function(){
var name = $("#name").val();
localStorage.setItem("name", name);
});
});
10. null和undefined的区别是什么
console.log(null==undefined)//true
console.log(null===undefined)//false
null
:Null类型,代表“空值”,代表一个空对象指针 (null是javascript的关键字,可以认为是对象类型,它是一个空对象指针,和其它语言一样都是代表“空值”,不过 undefined 却是javascript才有的。)
undefined
:Undefined类型,当一个声明了一个变量未初始化时,得到的是undefined。(undefined是访问一个未初始化的变量时返回的值,而null是访问一个尚未存在的对象时所返回的值。因此,可以把undefined看作是空的变量,而null看作是空的对象。)
undefined表示“缺少值”,此处应该有一个值,但是还没有定义。
null表示“没有对象”,该处不应该有值。
11. new操作符的作用是什么
new操作符首先,创建了一个空对象:
var obj = new Object();
设置原型链:
obj._proto_ = Object.prototype
示例代码了解new的作用:
function da(name) {
this.name = name;
}
da.prototype.sayName = function() {
console.log(this.name);
}
const jeskson = new da('dada');
console.log(jeskson.name); // dadajeskson.sayName(); // dada
由例子得出:
new 通过构造函数 da 创建出来的实例可以访问到构造函数中的属性
new 通过构造函数 da 创建出来的实例可以访问到构造函数原型链中的属性,(通过new操作符,实例与构造函数通过原型链连接了起来)
如果给构造函数一个return返回值,(没有显式的return任何值,默认返回undefined)
function da(name) {
this.name = name; return 1;
}
const jeskson = new da('dada');
console.log(jeskson.name); // dada
这个返回值没有任何的用处,构造函数如果返回原始值,这个返回值没有意义。
function da(name) {
this.name = name;
console.log(this); // da {name: 'dada'}
return {
age:1}
}
const jeskson = new da('dada');
console.log(jeskson); // {age:1}
console.log(jeskson.name); // undefined
构造函数如果返回值为对象,那么这个返回值就会被正常使用。
- new 操作符会返回一个对象
- 这个对象,也就是构造函数中的this,可以访问到挂载在this上的任意属性
- 这个对象可以访问到构造函数原型上的属性
- 返回原始值会忽略,返回对象会正常处理
12. JavaScript延迟加载的方式有哪些
js的延迟加载有助于提高页面的加载速度
延迟有:defer属性,async属性,动态创建DOM方式,使用JQuery的getScript方法,使用setTimeout延迟方法,让JS最后加载。
使用setTimeout延迟方法
<script type="text/javascript" >
function A(){
$.post("/lord/login",{
name:username,pwd:password},function(){
alert("Hello");
});
}
$(function (){
setTimeout('A()', 1000); //延迟1秒
})
</script>
13. call()和apply()的区别和作用是什么
call(), applay() 都属于Function.prototype的一个方法,它是JavaScript引擎内实现的,属于Function.prototype,所以每个Function对象实例,每个方法都有call,apply属性。
call()和apply() ,它们的作用都是相同的,不同的在于,它们的参数不同。
call(this, arg1, arg2, arg3);
apply(this, arguments);
function add(a,b){
console.log(a+b);
}
function sub(a,b){
console.log(a-b);
}
add.call(sub, 2, 1);
add.apply(sub, [2,1]);
对于A.applay(B)或A.call(B),简单地说,B先执行,执行后根据结果去执行A,用A去执行B的内容代码,再执行自己的代码。
var f1 = function(a,b) {
console.log(a+b);
}
var f2 = function(a,b,c) {
console.log(a,b,c);
}
f2.apply(f1,[1,2]) // 1 2 undefined
解析一下就是,先执行f1,f1执行后,这里注意f1是f1,不是f1()执行方法,所以里面的console.log等内容代码并没有执行,相等于,初始化了代码f1,由于没有返回值,结果是undefined,f2执行的时候this指向window。参数中为[1,2]
,解析后参数为1,2,undefined;执行f2方法后,打印出结果值为:1 2 undefined
A.call(B, 1,2,3) 后面的参数都是独立的参数对象,会被自动解析为A的参数:
var f1 = function(a,b) {
console.log(a+b);
}
var f2 = function(a,b,c) {
console.log(a,b,c);
}
f2.call(f1,[1,2]); // [1,2] undefined undefined
f2.call(f1, 1, 2); // 1 2 undefined
解析一下就是,参数中的[1,2]
,因为传入了一个数组,相当于只传入了第一个参数,b和c参数没有传。
使用apply()和call():
//apply用法
var arr = new Array(1,2,3)
var arr1 = new Array(11,21,31)
Array.prototype.push.apply(arr,arr1)
console.log(arr)//[1, 2, 3, 11, 21, 31]
//call用法
var arr = new Array(1,2,3)
var arr1 = new Array(11,21,31)
Array.prototype.push.call(arr,arr1[0],arr1[1],arr1[2<