简介:
JavaScript作为Web前端开发的核心语言,其面试题目覆盖了从基础语法到复杂的异步编程、内存管理等多个层面。本文精心整理了JavaScript高频面试题,涵盖闭包、事件循环、Promise、模块化、原型链、this
关键字等关键知识点。通过对每个问题的详细讲解与代码示例,本篇文章将为你提供一套全面的备考指南,助力你在面试中脱颖而出。
🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥
温馨提示:标题⭐越多,说明遇到的概率越大哦!
说说JS原型和原型链⭐⭐⭐
原型:函数都要prototype(显示原型)属性,而prototype会自动初始化一个空对象,这个对象就是原型对象,
原型对象中会有一个constructor属性,这个属性将指向了函数本身,
实例化对象都有一个_proto_(隐式原型)属性,_proto_属性指向原型对象
原型链:从实例对象往上找构造这个实例的相关对象,然后这个关联的对象再往上找,找到创造它的上一级的原型对象,以此类推,一直到object.prototype原型对象终止,原型链结束.
原型链中的原型对象中的内容,是会被不同的实例,所共有的
什么是闭包?闭包有什么作用?⭐⭐⭐
简单来说:函数跨作用于寻找变量就会形成闭包。
由于在js中,变量到的作用域属于函数作用域,在函数执行后作用域会被清除、内存也会随之被回收,但是由于闭包是建立在一个函数内部的子函数,由于其可访问上级作用域的原因,即使上级函数执行完,作用域也不会随之销毁,这时的子函数---也就是闭包,便拥有了访问上级作用域中的变量权限,即使上级函数执行完后,作用域内的值也不会被销毁。
闭包解决了什么:
在本质上,闭包就是将函数内部和函数外部连接起来的一座桥梁。
由于闭包可以缓存上级作用域,那么就使得函数外部打破了“函数作用域”的束缚,可以访问函数内部的变量。以平时使用的Ajax成功回调为例,这里其实就是个闭包,由于上述的特性,回调就拥有了整个上级作用域的访问和操作能力,提高了几大的便利。开发者不用去写钩子函数来操作审计函数作用域内部的变量了。
闭包有哪些应用场景:
闭包随处可见,一个Ajax请求的成功回调,一个事件绑定的回调函数,一个setTimeout的延时回调,或者一个函数内部返回另一个匿名函数,这些都是闭包。简而言之,无论使用何种方式对函数类型的值进行传递,当函数在别处被调用时都有闭包的身影
闭包的缺陷:
由于闭包打破了函数作用域的束缚,导致里面的数据无法清除销毁,当数据过大时会导致数据溢出
new操作符在创建实例的时候经历了哪几个阶段⭐⭐⭐
new创建了一个对象,共经历了4个阶段:
- 创建一个空对象
- 设置原型链
- 让实例化对象中的this指向对象,并执行函数体
- 判断实例化对象的返回值类型
call和apply的区别和作用?⭐⭐⭐
apply和call都是调用一个对象的一个方法,用另一个对象替换当前对象。
相同点:方法的含义是一样的,即方法功能是一样的。并且第一个参数的作用是一样的
不同点:call可以传入多个参数、apply只能传入两个参数,所以其第二个参数往往是作为数组形式传入
存在意义:实现(多重)继承
什么是防抖和节流?如何实现?⭐⭐⭐
防抖:多次触发某事件时,只执行最后一次。例如,搜索框输入时延迟发送请求。
节流:多次触发某事件时,限定在一定时间内只执行一次。例如,窗口滚动时控制滚动事件的触发频率。
// 防抖
function debounce(fn, delay) {
let timer;
return function(...args) {
clearTimeout(timer);
timer = setTimeout(() => fn.apply(this, args), delay);
};
}
// 节流
function throttle(fn, delay) {
let last = 0;
return function(...args) {
let now = Date.now();
if (now - last > delay) {
last = now;
fn.apply(this, args);
}
};
}
如何解释JavaScript中的异步编程模型?Promise 是什么?⭐⭐⭐
JavaScript是单线程的,异步编程模型包括回调、Promise 和 async/await。
Promise 是异步操作的占位符,用来处理异步操作的结果,
状态包括 pending(待定)、fulfilled(已完成)、rejected(已拒绝)。
如何使用 async 和 await 处理异步操作?⭐
async
函数返回一个 Promise
,可以在函数内部使用 await
等待异步操作完成。例子:
async function fetchData() {
try {
let response = await fetch('https://api.example.com/data');
let data = await response.json();
console.log(data);
} catch (error) {
console.error(error);
}
}
深拷贝和浅拷贝的问题⭐⭐
- 深拷贝和浅拷贝值针对Object和Array这样的复杂类型
- a和b指向了同一块内存,所以修改其中任意一个值,另外一个值也会随之变化,这是浅拷贝
- a和b指向同一块内存,但是修改其中任意一个值,另外一个调用的变量,不会受到影响,这是深拷贝
- 浅拷贝:“Object.assign()”方法用于将所有可枚举的属性的值从一个或多个源对象复制到目标对象,它将返回目标对象
- 深拷贝:JSON.parse( )和JSON.stringify( )给了我们一个基本的解决办法。但是函数不能被正确处理
js中this的用法(经典)⭐⭐
this是js的关键字,随着函数使用场合不同,this的值会发生变化。但是总有一个原则,那就是this指的是调用函数的那个对象。
//纯粹的函数调用,this指向全局
function test(){
// this.x = 1;
console.log(this);
}
test();
//作为方法调用,那么this就是指向这个上级对象
function test1(){
console.log(this)
}
var o = {}
o.x = test1;
o.x()
//构造函数调用,就是生成一个新的对象,这里的this指向这个对象
function test2(){
console.log(this)
}
var m = new test2();
//apply调用
//this指向的事apply中的第一个参数
var x = 0;
function test3(){
console.log(this.x);
}
var o = {};
o.x = 1;
o.m =test3;
o.m.apply(); //0
o.m.apply(o); //1
js中this的指向:⭐⭐
普通函数调用: 在普通函数调用中(不属于对象方法的调用),this 指向全局对象(浏览器中是 window,在 Node.js 中是 global)。如果在严格模式下(use strict),this 的值为 undefined。
function example() {
console.log(this); // 在非严格模式下指向全局对象,严格模式下为 undefined
}
example();
对象方法调用: 当函数作为对象的方法调用时,this
指向调用该方法的对象,即方法前面的对象。
const obj = {
name: 'Alice',
greet: function() {
console.log(this.name); // 'Alice'
}
};
obj.greet(); // this 指向 obj
构造函数调用: 当使用 new
关键字调用函数时,this
指向新创建的实例对象。
function Person(name) {
this.name = name;
}
const person1 = new Person('Alice');
console.log(person1.name); // 'Alice'
call
/ apply
/ bind
调用: 通过 call
或 apply
方法可以显式地指定 this
的指向,bind
则返回一个绑定了指定 this
值的新函数。
function greet() {
console.log(this.name);
}
const person = { name: 'Alice' };
greet.call(person); // 'Alice'
greet.apply(person); // 'Alice'
const boundGreet = greet.bind(person);
boundGreet(); // 'Alice'
箭头函数中的 this
: 箭头函数没有自己的 this
,它的 this
继承自外层作用域中的 this
,即定义箭头函数时所在的上下文环境中的 this
。
const obj = {
name: 'Alice',
greet: () => {
console.log(this.name); // 箭头函数中的 this 是外层作用域的 this,在这里是全局对象
}
};
obj.greet(); // undefined
如何解释this在js中起的作用?⭐⭐
Js中的this,一般取决于调用这个函数的方法
1/如果函数被实例化(new 构造函数名())的情况下,this指向全新的对象
2/如果是某标签触发什么事件,调用了这个函数,this指向标签(整个DOM节点,包含它的子元素);
3/如果函数使用了call/apply,this是作为参数传入对象
4/有时候this指向不明确的话,this会指向window,ES6中的箭头函数修改了this指向,永远指向作用域
JS数据类型有哪些?⭐
栈: (原始数据) string/number/boolean/null/undefined/symbol
堆: (引用数据类型)object(array和函数属于object)
数据类型一共7种(6种基本类型+1种引用类型)
介绍JS有哪些内置对象?⭐
object是Javascript中所有对象的父对象
数据封装类对象:Object、Array、Boolean、Number和String
其他对象:Function、Arguments、Math、Date、RegExp、Error
栈与堆的区别?⭐
栈与堆的储存位置不同;
原始数据是储存在栈中简单数据段,体积小,大小固定,属于频繁使用的数据。
引用数据类型是储存在堆中的对象,占据的空间大,如果储存在栈中会影响运行性能,引用数据类型在栈中指明了自己的所在地。当代码解析时,会先从栈中获取地址,然后再从堆中获取实体。
事件代理(事件委托)⭐
事件代理是将子元素的事件写一个父元素,让父元素代替处理,内部使用e.target,e.target就是触发这个事件的子元素
事件委托又叫事件代理,是指利用事件冒泡的特性,将本应该注册在子元素上事件注册在父元素上,由父元素来处理子元素的事件。这样的好处是:
(1)有助于降低DOM操作,提高性能。
(2)增加扩展性,子元素可以随意增加,不用再做其他额外的操作,都在父元素上做事件处理操作
(3)有助于代码简洁高效性,不用为每个子元素添加事件处理程序,简化了代码处理逻辑。
事件的各个阶段⭐
捕获阶段 ---> 目标阶段 ---> 冒泡阶段
document ---> target目标 ---> document
由此addEventListener的第三个参数设置为true和false的区别已经非常清晰了
true--->代表该元素在事件的”捕获阶段”(由外向内传递)响应事件
false --->表示该元素在事件的”冒泡阶段”(由内向外传递)响应事件
父元素和子元素分别有点击事件的情况下⭐
点击父元素只会触发父元素事件,不会影响到子元素,如果点击子元素,会因为冒泡触发父元素的点击事件,可是阻止默认冒泡事件;
ForEach和map的区别在哪里⭐
这两个API都可以遍历数组
forEach函数,是给数组中的每个都执行一遍回调函数,不会返回一个值
Map方法是通过调用数组中的每个元素,映射到新元素中,从而创建一个新数组
==与===的区别?⭐
===为等同符,当左边与右边的值与类型都完全相等时,会返回true;
==为等值符,用来判断值是否相同,不会判断类型是否相同
null和undefined的区别?⭐
null用来表示尚未存在的对象,常用来表示函数企图返回一个不存在对象
undefined主要指定义了变量,但是并未赋值
NAN (not a Number)不是一个明确数值的数字类型
ECMAScript认为undefined是从null派生出来的,他们的值是一样的,但是类型却不一样。
所以
null == undefined //true
null === undefined //false
如何检查对象中是否存在某个属性?⭐
检查对象中存在某个属性的方式有三种:
第一种:使用in操作符号, 返回true或false:
console.log(“属性名” in 对象名);
第二种:使用hasOwnProperty方法, 会返回true或false:
console.log(对象.hasOwnProperty(“属性名”));
第三种:是够括号符号obj[‘属性名’],如果属性存在, 则返回该属性的值,如果不存在,则返回undefined
如果用原生js给一个按钮绑定两个click事件?⭐
使用事件监听,可给一个DOM节点绑定多个事件(addEventListener)
拖拽会用到哪些事件?⭐
dragstart---拖拽开始时在被拖拽元素上触发此事件,监听器需要设置拖拽所需数据,操作系统拖拽文件到浏览器时不触发此事件
dragenter---拖拽鼠标进入元素时在该元素上触发,用于给拖放的元素设置视觉反馈,如高亮
dragover---拖拽时鼠标在目标元素上移动时触发,监听器通过组织浏览器默认行为设置元素为可拖放元素
dragleave---拖拽时鼠标移出目标元素时在目标元素上触发,此时监听器可以取消掉前面设置的视觉效果
drag---拖拽期间在被拖拽元素上连续触发
drop---鼠标在拖放目标上释放时,在拖放目标上触发,此时监听器需要收集数据并且执行所需操作,如果是从操作系统拖放文件到浏览器,需要取消浏览器默认行为
dragend---鼠标在拖放目标上释放时,在拖拽元素上触发,将元素从浏览器拖放到操作系统时不会触发此事件
一次性插入1000个div,如何优化插入的性能⭐
使用Fragment(document.createDocumentFragment( ))
Documentfragments是DOM节点,它们不是DOM树的一部分。通常的用例是创建文档片段,将元素附加到文档片段,然后将文档片段附加到DOM树中。因为文档片段存在于内存中,并不在DOM树中,所以讲子元素插入到文档片段时不会引起页面回流。因为使用文档片段会带来更好的性能
先创建一个div,后续的复制这个元素,避免重复创建元素,再放到元素片段里面
var divFragment = document.createDocumentFragment();
let div = document.createElement(“div”);
for(var i = 0; i <1000; i ++){
divFragment.append(div.cloneNode())
}
document.body.appendChild(divFragment);
typeof和instanceof有什么区别?⭐⭐
typyof用于判断数据的基本类型
instanceof用于判断一个变量是够属于某个对象的实例。也可以用来判断某个构造函数的prototype属性是否存在另外一个要检测对象的原型链上
简述同步和异步的区别?⭐
同步就是指一个进程在执行某个请求的时候,若该请求需要一段时间才能返回信息,那么这个进程将会一直等待下去,直到返回信息之后才继续执行后面的代码
异步是指进程不需要一直等下去,而是继续执行下面的操作,不管其他进程的状态。当有消息返回时系统会通知进程进行处理,这样可以提高执行的效率。
什么是伪数组,如何将伪数组转换为真数组?⭐
伪数组:无法直接调用数组方法或期望length属性有什么特殊的行为,不具有push、pop等方法,但仍可以对真正数组遍历方法来遍历它们。典型的是函数的argument参数等
伪数组的三大特性:
- 具有length属性
- 按索引方式存储数据
- 不具有数组的push,pop等方法
将伪数组转化为标准数组需要用到数组原型中的方法 slice
例如: Array.prototype.slice.call( 伪数组名称 );
判断是否为数组的方法⭐
console.log( arr instanceof Array )
console.log( arr.construct == Array)
console.log( Array.isArray( arr ))
JS垃圾回收方式⭐
标记清除:这是js最常用的垃圾回收方法,当一个变量进入执行环境时,例如函数中声明一个变量,将其标记为进入环境,当变量离开环境时,(函数执行结束),标记为离开环境
引用计数:跟踪记录每个值被引用的次数,声明一个变量,并将引用类型复制给这个变量,则这个值的引用次数+1,当变量的值变成了另一个,则这个值的引用次数-1,当这个值的引用次数为0的时候,就回收
JS的各种位置,比如:clienHeight、scrollHeight、offsetHeight以及scrollTop、offsetTop、clientTop的区别?⭐
clientHeight: 表示的是可视区域的高度, 不包含border和滚动条
offsetHeight: 表示可是区域的高度, 包含了border和滚动条
scrollHeight: 表示了所有区域的高度, 包含了滚动条被隐藏的部分
clientTop: 表示边框border的厚度, 在为指定的情况下,一般为0
scrollTop: 滚动后被隐藏的高度, 获取对象相对于offsetParent属性指定的父坐标距离顶部的距离
展开(Spread)运算符和剩余(Rest)运算符有什么区别?⭐
展开运算符(spread)是三个点(...),可以将一个数组转为用逗号分隔的参数序列。说的通俗易懂一点,有点像化骨绵掌,把一个大元素给打散成一个个单独的小元素
剩余运算符也是用三个点(...)表示, 它的样子看起来和展开运算符一样,但是它是用于结构数组和对象. 在某种程度上, 剩余元素和展开元素相反, 展开元素会”展开”数组变成多个元素, 剩余元素会收集多个元素和”压缩”成一个单一的元素
// 拓展运算符
let arr = ["阿里巴巴",'阿里妈妈','蚂蚁金服'];
console.log(...arr); //阿里巴巴 阿里妈妈 蚂蚁金服
//剩余运算符
let [webName,...rest] = ["阿里巴巴",'阿里妈妈','蚂蚁金服'];
console.log(webName); //阿里巴巴
console.log(rest); //(2) ["阿里妈妈", "蚂蚁金服"]
arguments的对象是什么?⭐
arguments对象是函数中传递的参数值合集.它是一个类似数组的对象,因为它有一个length属性, 我们可以使用数组索引表示法arguments[1]来访问单个值, 但它没有数组的内置方法, 如forEach/reduce/filter和map, 我们可以利用Array.prototype.slice
什么是回调函数? 回调函数有什么缺点?⭐
回调函数是一段可执行的代码段, 它作为一个参数传递给其它的代码, 其作用是在需要的地方方便调用这段( 回调函数 )代码
在javascript中函数也是对象的一种, 同样对象可以作为参数传递给函数, 因此函数也可以作为参数传递给另外一个函数, 这个作为参数的函数就是回调函数
js中eval()函数的作用⭐
eval()可以接受一个字符串str作为参数,并把这个参数作为脚本代码来执行。
结语
🔥如果文章对你有帮助的话,欢迎💗关注、👍点赞、⭐收藏、✍️评论,支持一下小老弟,蟹蟹大咖们~