前端基础-JavaScript面试题汇总

1. js有哪几种数据类型?如何判断数据的类型?

基本类型:Number, String, Boolean, null, undefined, Symbol(ES6新增)
引用类型:数组,对象,函数(都是对象类型)
使用typeof来判断,有局限性。数组、对象用typeof判断都是object类型
使用Object.prototype.toString.call()来判断,比较完美

typeof NaN // "number"
typeof function() {} // "function"
typeof [] // "object"
typeof {} // "object"

Object.prototype.toString.call(NaN) // "[object Number]"
Object.prototype.toString.call([]) // "[object Array]"

2. js创建对象的几种方式

var obj = {}
var obj = new Object()
var obj = Object.create(Object.prototype) // Object.create(obj) 创建一个新对象,新对象的__proto__为obj

3. 深浅拷贝

常见的拷贝对象方法如Object.assign(obj, obj1),扩展运算符...都是浅拷贝
JSON.parse(JSON.stringify(obj))是深拷贝。但当obj为undefined或者函数时,会报错
深拷贝可以使用lodash库的cloneDeep(obj)方法
或者自己封装一个深拷贝函数。主要实现原理就是:如果是基本数据类型,直接复制即可。如果是引用类型,就遍历所有项,如果是基本类型,直接复制;如果是引用类型,执行递归操作。
具体实现见另一篇文章:JavaScript深浅拷贝

4. 变量提升

函数声明与变量声明经常被JavaScript引擎隐式地提升到当前作用域的顶部
如果变量名与函数名同名,则函数声明会覆盖变量声明
变量提升的只是声明部分,不包括赋值部分

console.log(fun) // 打印结果为: f fun() { console.log(2) }
var fun = 3
function fun() {
    console.log(2)
}

5. 作用域链

js变量作用域分函数作用域和全局作用域。
函数执行时,从当前作用域开始搜,没有找到的变量,会向上层查找,直至全局函数。这就是作用域链。

6. 闭包

函数嵌套,内部函数能够访问外部函数的局部变量。

function fun() {
    var count = 2
    return function() {
        console.log(count)
    }
}
fun()

优点:长外部函数局部变量生命周期
缺点:容易造成内存泄漏,被内部函数访问的变量需要手动回收

7. this指向

普通函数,this指向调用它的那个对象
箭头函数,没有具体的this,它的this相当于是从上下文继承的,也就是说定义时候的上下文this.(使用call,apply等任何方式都无法改变this的指向)

8. call, apply, bind

call, apply和bind是Function原型上的三个方法,都是为了改变函数体内部this的指向。
call、apply、bind 三者第一个参数都是this要指向的对象,后面的参数call是一个个的参数列表,apply则是放到数组中的
bind 是返回一个函数,便于稍后调用。call, apply则是立即调用

9. 原型链

每个函数都有一个prototype属性,是一个对象,我们称之为原型对象。原型对象有个constructor属性,指向这个函数。
prototype上的属性和方法可以被实例对象调用。
prototype还可以用来实现继承,也就是原型链继承。 只需要将子类构造函数的原型指向父类的一个实例
每个对象都有__proto__属性,指向其构造函数的原型对象。当我们使用对象的属性时候,如果本身不包含某个属性,就会到其构造函数的原型上查找,而构造函数的原型也是对象,也可以向上查找,直到null为止

10. js如何实现继承

// 构造函数继承
function Student() {
    Person.call(this) // 只能继承父类构造函数里面的属性,不能继承原型上的属性和方法
}

// 原型链继承
function Student() {}
Student.prototype = new Person() // 引用类型的原型属性会被所有的实例共享

// 组合继承,二者结合
function Student() {
    Person.call(this);
}
Student.prototype = new Person() // 调用了两次父类构造函数,生成了两份实例(子类实例的属性,在子类实例的__proto__中也存在)

// 寄生组合继承
function Student() {
    Person.call(this)
}
(function(){
    let Super = function() {}
    Super.prototype = Person.prototype
    Student.prototype = new Super() // 比较完美,但时间较为复杂
})()

// class继承
class Student extend Person() {}

11. new操作符具体干了什么

创建一个空对象
由this变量引用该对象
该对象继承构造函数的原型(更改this指向)
把属性和方法加入到this引用的对象
最后隐式的返回this

let obj = {}
obj.__proto__ = Person.prototype
Person.call(obj)

12. js如何实现一个类?

// 使用构造函数,方法需要写在构造函数的原型链上
function Person(name, age) {
    this.name = name
    this.age = age
}
Person.prototype.say = function() {
    console.log(this.name)
}
var person = new Person('zs', 18)
person.say()
// 使用class
class Person {
    constructor(name, age) {
        this.name = name
        this.age = age
    }
    say() {
        console.log(this.name)
    }
}
var person = new Person('zs', 18)
person.say()

13. 事件队列

js执行时,遇到同步任务,就将同步任务按照执行顺序排列到执行栈中。
遇到异步任务,会将此类异步任务挂起,继续执行执行栈中的任务。等异步任务返回结果后,再按照顺序排列到事件队列中。
主线程先将执行栈中的同步任务清空,然后检查事件队列中是否有任务,如果有,就将第一个事件对应的回调推到执行栈中执行,若在执行过程中遇到异步任务,则继续将这个异步任务排列到事件队列中。
主线程每次将执行栈清空后,就去事件队列中检查是否有任务,如果有,就每次取出一个推到执行栈中执行,这个循环往复的过程被称为“Event Loop 事件循环”。

14. 宏任务与微任务

(1) 宏任务:setTimeout(), setInterval(), requireAnimationFrame()
宏任务队列所处的队列就是宏任务队列
第一个宏任务队列中只有一个任务:执行主线程js代码
宏任务队列可以有多个
(2) 微任务:new Promise().then(), process.nextTick()
微任务所处的队列就是微任务队列
只有一个微任务队列
上一个宏任务队列执行完毕后,如果有微任务队列,就会执行微任务队列里的所有任务

15. js事件传播机制

js事件传播有三个阶段:事件捕获、目标阶段、事件冒泡
事件捕获:从外到内进行事件传播
目标阶段:事件传播到事件目标
事件冒泡:从内到外进行事件传播
一般情况下,默认都是冒泡阶段触发事件,因此事件触发的顺序是从内到外。
取消默认事件:W3C的方法是e.preventDefault(),IE则是使用e.returnValue = false

16. 什么是事件代理?它有什么好处?

利用事件冒泡的原理,把事件加到父级上,触发执行效果
好处:减少事件数量,提高性能
新添加的元素,依然可以触发该事件

17. 如何阻止事件冒泡

阻止冒泡:W3C的方法时ev.stopPropagation(), 早期IE的方法是ev.cancelBubble = true。
需要注意的是ev.stopPropagation()是用来阻止事件传播的,也可以在捕获阶段使用。

18. cookies与session

cookie、session都是用来记录用户状态的。不同的是cookie保存在客户端,session保存在服务器。
具体来说,用户登录网站,服务器会通过response给客户端一个cookie。
客户端浏览器会把cookie存起来,等下次再次请求该网站的时候,会把请求的url和cookie一同提交给服务器。
服务器检查该Cookie, 以此来辨认用户状态。 服务器还可以根据需要修改Cookie的内容。
session保存在服务器上。 客户端浏览器访问服务器的时候, 服务器把客户端信息以某种形式记录在服务器上。
这就是session。 客户端浏览器再次访问时只需要从该session中查找该客户的状态就可以了。

19. localstorage与sessionstorage

localStorage和sessionStorage一样都是用来存储客户端临时信息的对象。
sessionStorage用于本地存储一个会话(session)中的数据,这些数据只有在同一个会话中的页面才能访问
并且当会话结束后数据也随之销毁。因此sessionStorage不是一种持久化的本地存储,仅、是会话级别的存储。只允许同一窗口访问。
而localStorage用于持久化的本地存储,除非主动删除数据,否则数据是永远不会过期的。同源可以读取并修改localStorage数据。

20. Proxy与Reflect

Proxy 对象用于创建一个对象的代理, 从而实现基本操作的拦截和自定义
Reflect 是一个内置的对象, 它提供拦截 JavaScript 操作的方法。
Reflect并非一个构造函数, 所以不能通过new运算符对其进行调用,
或者将Reflect对象作为一个函数来调用。 Reflect的所有属性和方法都是静态的( 就像Math对象)。

21. js垃圾回收机制知道哪些

标记清除:垃圾回收期会在运行的时候给存储在内存中的所有变量加上标记,然后去掉环境中的变量,以及被环境中变量引用的变量(闭包)的标记。在完成这些后依然存在的标记就是要删除的变量。
引用计数:引用计数的策略是跟踪记录每个值被使用的次数。当声明了一个变量,并将一个引用类型赋值给该变量时,这个值的引用次数就加1。如果该变量的值变成了另外一个,则这个值的引用次数减1。当这个值的引用次数为0的时候,就可以将它占用的空间回收。
IE中JavaScript对象通过标记清除的方式就行垃圾回收,但是BOM和DOM对象是用引用技术的方式回收的。也就是说,只要是涉及BOM和DOM,就有可能出现循环引用问题。

22. 节流和防抖

// 节流函数
// 应用场景: 表单提交(频繁点击按钮,只保存一次)
function throttle(fn, delay) {
    var lastTime = 0
    return function() {
        var nowTime = Date.now()
        if (nowTime - lastTime > delay) {
            fn()
            lastTime = nowTime
        }
    }
}
// 防抖函数
// 应用场景:邮件校验,输入框模糊查询(等输入完了在执行查询)
function debounce(fn, delay) {
    var timer = null
    return function() {
        clearTimeout(timer)
        timer = setTimeout(fn, 500)
    }
}

23. script标签中defer与async的区别

没有defer或async属性,浏览器会立即下载并执行相应的脚本,并且在下载和执行时页面的处理会停止。
有了defer属性,浏览器会立即下载相应的脚本,在下载的过程中页面的处理不会停止,等到文档解析完成后脚本才会执行。
有了async属性,浏览器会立即下载相应的脚本,在下载的过程中页面的处理不会停止,下载完成后立即执行,执行过程中页面处理会停止。
如果同时指定了两个属性,则会遵从async属性而忽略defer属性。


24. 什么是内存泄漏,哪些操作会造成内存泄漏?

内存泄漏指的是不再使用或需要的数据,仍然存在于内存中。
以下操作会造成内存泄漏:
setTimeout的第一个参数使用字符串而不是函数
闭包
控制台日志
对象循环引用(两个对象彼此引用且彼此保留,会产生一个循环)

25. let、const、var

let声明后会形成一个封闭作用域,使得变量只在这个块作用域内有效
let不存在变量提升,不可重复声明。
const代表是一个常量,一旦声明,必须初始化。同样会形成一个封闭作用域。
const 保证的不是变量的值不变,而是变量指向的内存地址的数据不允许改动。对于引用类型(对象 object,数组 array,函数 function),变量指向的内存地址其实是保存了一个指向实际数据的指针,所以 const 只能保证指针是固定的,至于指针指向的数据就无法控制了,所以使用 const 声明引用类型时要慎重。

26. Promise是什么,Promise作用?

Promise是异步编程的一种解决方案。
具体表达:
语法上来说,Promise是一个构造函数
功能上来说,Promsie对象用来封装一个异步操作并获取其结果

Promise作用:
指定回调函数的方式更加灵活
回调函数:必须在启动异步任务前指定
Promise: 启动异步任务,返回Promise对象,给Promise对象绑定回调函数(可以在异步任务结束后执行)
支持链式调用,解决回调地狱问题。
回调地狱:不便于阅读,不便于异常处理
终极解决方案:async...await(完全没有回调函数)
关于Promise,可以参考另一篇文章:自定义Promise

27. async...await

async...await,是异步程的一种解决方案,与Promise有很大关联。
async函数返回一个Promise对象。可以使用then方法添加回调函数。
await用于等待一个Promise对象,只能在async函数内部使用。
await语句的返回值跟await后的表达式相关。
表达式为Promise对象:await会暂停执行,等待Promise对象resolve,然后返回解析值并回复async函数的执行。
非Promise对象:直接返回对应的值。
关于async...await,可以参考另一篇文章:async...await

28. Iterator作用,它的遍历过程是什么

Iterator为不同的数据结构提供统一的访问机制。部署了Iterator接口的数据结构能够使用for...of来进行遍历。
数组、Set和Map原生具备Iterator接口,不用任何处理,可以被for...of遍历。原因在于,它们部署了Symbol.iterator属性。
凡是部署了Symbol.iterator属性的数据结构,就称为部署了遍历器接口。

Iterator 的遍历过程:
(1)创建一个指针对象,指向当前数据结构的起始位置。也就是说,遍历器对象本质上,就是一个指针对象。
(2)第一次调用指针对象的next方法,可以将指针指向数据结构的第一个成员。
(3)第二次调用指针对象的next方法,指针就指向数据结构的第二个成员。
(4)不断调用指针对象的next方法,直到它指向数据结构的结束位置。

29. 说说对模块化开发的认识

30. ES6对象新增的方法

Object.is()比较两个值是否相等,与严格的===基本一致。不同之处只有两个,如下:

Object.is(+0, -0) // false
Object.is(NaN, NaN) // true
+0 === -0 // true
NaN === NaN // false

Object.assign()用于对象的合并
Object.keys()返回对象的键组成的数组
Object.values()返回对象的值组成的数组
Object.entries() 返回一个数组,成员是键值对组成的数组

const obj = { a: 1, b: 2 }
Object.entries(obj) // [['a', 1], ['b', 2]]

Object.fromEntries()Object.entries()的逆操作,用于将一个键值对数组转换为对象

31. ES6数组新增的方法

Array.from()将两类对象转为真正的数组:类似数组的对象(array-like object)和可遍历(iterable)的对象(包括 ES6 新增的数据结构 Set 和 Map)
Array.of() 将一组值,转换为数组
Array.prototype.find()找出第一个符合条件的数组成员,如果没有,返回undefined
Array.prototype.findIndex() 返回第一个符合条件的数组成员的位置,如果所有成员都不符合条件,则返回-1。
Array.prototype.fill()
Array.prototype.includes() 数组是否包含某个元素
Array.prototype.flat() 数组扁平化
entries(),keys()和values()用于遍历数组,他们都返回一个遍历器对象。keys()是对键名的遍历,values()是对值的遍历,entries()是对键值对的遍历。

Array.from(new Set([1, 2])) // [1, 2]
Array.of(1, 2, 3) // [1, 2, 3]
new Array(4).fill('1') // [1, 1, 1, 1]
[1, 2, [3, 4]].flat() // [1, 2, 3, 4]

32. ES11新特性

(1)?.链合并运算符

let person = {
    info: {
        name: 'Jack',
        age: 18
    }
}
let name = person?.info?.name

(2)??null判断运算符,只有左侧为null或者undefined时,才会返回右侧的值

let name = person?.info?.name ?? 'Jack'

(3)动态导入import(),适用于按需加载,条件加载
返回一个Promise对象,加载成功后,模块会作为一个对象,当做then回调的参数

(4)bigint js只能精确到53个二级制位置

typeof 1n // bigint

(5)Promise.allSettled()接收一组Promise作为参数,必须等所有的promise返回结果后,才会结束
(6)String.prototype.matchAll()返回一个正则表达式在当前字符串中所有的匹配
(7)globalThis 获取顶层对象,无论实在浏览器还是Node里

33. 基本类型与引用类型区别。为什么引用类型存储在堆里?

基本类型存储在栈中。
引用类型会在栈中保存一个引用(指针),实际内容存储在堆中。当我们修改一个引用内容的时候,其他引用这块内容的变量,结果也会发生变化。
就查询速度而言,栈远快于堆
stack创建时候,大小是确定的,超过额度大小就会发生栈溢出【当js出现死循环或者错误的递归时候】。
heap大小是不确定的,需要可以一直累加。

参考资料:
2020前端基础包过面试题
script标签中的defer和async属性

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,189评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,577评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,857评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,703评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,705评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,620评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,995评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,656评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,898评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,639评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,720评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,395评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,982评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,953评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,195评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,907评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,472评论 2 342