这里我主要是在博客上搜索了前端面试相关的一些面试题目合集,并把其答案一一整理在了题目下方,有一些不好理解的也做了简单的解释。在最后有纯题目版
JS前端面试题
1.JS数据类型
- JS的基本数据类型有8种:
number,string,boolean,undefined,object,Null,symbol,bigInt- ES5语法下只有六种:number,string,boolean,undefined,object,Null
- ES6语法下有八种:添加了 symbol , bigInt
- symbol:这种类型的对象永不相等,可以解决属性名冲突问题。
- bigInt:安全存储、操作大整数
- 基本数据类型:number,string,boolean,undefined,Null
- 引用数据类型:object,Array,Date,function
2.JS变量 和 函数声明 的 提升
- 变量提升:在当前的作用域中,js代码自上而下执行之前,浏览器会把所有带var / function 关键字进行提前声明或定义
- 带var的关键字只是提前声明一下
- 带function的关键字在变量提升阶段把定义和声明都完成了
- 函数提升——函数声明提升: 通过function声明的函数,在声明语句之前就可以直接调用
- js中函数有两种:函数声明式、函数字面量式(函数表达式、匿名函数),只有函数声明才存在函数提升
console.log(f1); // function f1() {}
console.log(f2); // undefined
function f1() {} // 函数声明式
var f2 = function() {} // 函数字面量式
真正的执行顺序是:
function f1() {} // 函数声明式
console.log(f1); // function f1() {}
console.log(f2); // undefined
var f2 = function() {} // 函数字面量式
- 变量提升和函数声明提升的优先级:函数会首先被提升,然后才是变量,函数不会被变量声明覆盖,但是会被变量赋值覆盖。
- 函数内部变量提升优先级高于函数外部变量提升
3.闭包
- 定义:能够读取其他函数内部变量的函数——可以理解为:定义在一个函数内部的函数
- 闭包是函数传递和作用域链同时使用造成的结果
var a = "全局环境";
function A() {
var a = "局部环境";
return {
B: function () {
console.log(a);
},
};
}
var obj = A();
obj.B(); // "局部环境"
现在,我们在函数A的内部返回了一个对象,该对象内部有一个函数B.我们在全局环境下调用了这个函数B,结果打印出了局部环境。这就是一个闭包。我们成功的在全局作用域下调用到了函数A作用域中的变量。
- 好处:可以读取函数内部的变量 、 变量始终都在内存中 、 封存对象的私有属性和私有方法
- 坏处:耗内存,使用不当会导致内存溢出
4.== 和 === 之间的区别
- == 非严格意义上的相等,值相等就行
- === 严格意义上的相等,数据类型和值都要相等
5.this
- 函数中,指向函数的直接调用者
- new中,指向new出来的对象
- 事件中,指向触发这个事件的对象
6.forEach 和 map
- 相同点:都是数组方法——循环遍历数组,匿名函数的this都是指向window,只能遍历数组
- 不同点:
- forEach只适合读出数组中数据,但不改变其中的值,比如打印或者存入
- map适合需要改变数组中的数值,速度更快,而且他会返回一个新的数组
7.箭头函数 和 普通函数 的区别
- 外形不同
function func(){
//code
}
let func=()=>{
//code
}
- 箭头函数都是匿名函数
// 具名函数
function func(){
// code
}
// 匿名函数
let func=function(){
// code
}
// 箭头函数全都是匿名函数
let func=()=>{
// code
}
- 不能使用new
function person( name , age ){
this.name = name ;
this.age = age ;
}
//普通函数可以new对象
let admin = new person( "xiaotu" , 21) ;
console.log(admin.name) ;
console.log(admin.age)
// xiaotu
// 21
- 箭头函数this的指向不同——箭头函数this指向声明的作用域
var name="xiaotu" ;
let func=()=>{
console.log(this.name) ;
}
func();
//xiaotu
// 箭头函数在全局作用域声明,this指向全局window
var name = "xiaotu1";
function wrap(){
this.name="xiaotu2";
let func=() => {
console.log(this.name);
}
func();
}
let en=new wrap();
//xiaotu2
//箭头函数在wrap作用域中声明,this指向wrap对象
- 不能使用argument【类数组】对象
let C = (...c) => {
console.log(c);
}
C(3,82,32,11323); // [3, 82, 32, 11323]
8.同源策略
同源指的是域名、协议、端口号相同
同源策略是一种约定,它是浏览器最核心也最基本的安全功能
- 存在的意义
- 非同源下的cookie等隐私数据可以被随意获取
- 非同源下的DOM可以随意操作
- ajax可以任意请求的话,用户的隐私肯定会泄露
- 同源策略的限制范围
- 不能获取不同源的cookie、localstorage、indexDB
- 不能获取不同源的 DOM()
- 不能发送不同源的 ajax 请求(可以向不同源的服务器发起请求,但是返回的数据会被浏览器拦截)
9.如何解决跨域
跨域:从一个域名的网页请求另一个域名的资源
- nodejs代理请求
- jsonp——json的一种使用形式
- 浏览器禁止JS跨域,但是
<script>
标签跨域请求。客户端使用script代替XHR(XMLHttpRequest)发起跨域请求。服务器在json响应数据前后填充一些额外的内容,就称为“填充式JSON”——JSONP
- 浏览器禁止JS跨域,但是
10.严格模式
- 变量先声明后使用
- 函数参数不能有同名属性
- 函数声明必须在顶层,不允许在非代码块内声明函数
- 禁止this指向全局对象(定时器的this还是指向window)
11.ES6新增
- let ——作用与var相似,用于声明变量
+ let不能重复声明变量
+ ES6 引入块级作用域,let声明变量在{}块级作用域内有效
+ let不存在变量提升 - const ——用来声明常量
- 解构赋值
ES6允许按照一定的模式从数组和对象中提取值
//数组的解构
const c = [1,2,3] ;
let [l , m , n] = c ;
//1=1 ; m=2 ; n=3
// 对象的解构
const liMing = {
name: 'liMing',
age: '22',
tell: function(){
console.log(`I am liMing`)
}
}
let {name, age, tell} = liMing
console.log(name) // 'liMing'
console.log(age) // '22'
console.log(tell) // f(){...}
tell() // I am liMing
- 箭头函数
- rest参数
ES6引入rest参数,用于获取函数的实参,用来代替arguments
// ES5获取实参的方式
function printStudent(){
console.log(arguments) // arguments为一个对象
}
printStudent('LiMing','HanMeimei')
// ES6获取实参的方式
function printFriend(friend1, friend2, ...rest){ // rest参数必须放在形参列表最后,否则会报错
console.log(friend1)
console.log(friend2)
console.log(rest) // 得到一个数组,可以使用数组api
}
printFriend('小猫','小狗','兔子','鸭子')
// 小猫
// 小狗
// ['兔子','鸭子']
- 扩展运算符
...
能将数组转化为逗号分隔的参数序列
const STUDENTS = ['小明','小芳','小红']
function printStudent(){
console.log(arguments)
}
printStudent(STUDENTS) // 参数为一个数组,数组内包含3个元素
printStudent(...STUDENTS) // 参数为3个元素
//数组合并
const STUDENTS1 = ['小明','小芳','小红']
const STUDENTS2 = ['小吴', '小王']
// es5写法
const STUDENTS_ES5 = STUDENTS1.concat(STUDENTS2)
// es6写法
const STUDENTS_ES6 = [...STUDENTS1, ...STUDENTS2]
- 两种数据类型 symbol bigInt
- Promise【重要,要专门去理解】
语法上Promise是一个构造函数,用来封装异步操作并可以获取其成功或失败的结果。可以避免回调地狱。 - 迭代器(iterator):是一种接口。任何一种数据结构,只要部署了iterator接口,就可以完成遍历操作
const food = ['鱼香肉丝','糖醋里脊','酸菜鱼']
for(let item of food){
console.log(item)
}
let iterator = food[Symbol.iterator]()
console.log(iterator.next()) // {value: "鱼香肉丝", done: false}
console.log(iterator.next()) // {value: "糖醋里脊", done: false}
console.log(iterator.next()) // {value: "酸菜鱼", done: false}
console.log(iterator.next()) // {value: undefined, done: true} true 表示遍历已经结束
- set(集合)
本质上是个对象,类似于数组,但是成员的值都是唯一的。集合实现了iterator接口,可以使用扩展运算符和for...of
进行遍历。
- size —— 元素个数
- add —— 添加元素
- delete —— 删除元素,返回 boolean 值
- has —— 检测集合中是否包含某个元素
- Map
Map——键值对的集合,Map也实现了iterator接口,所以可以使用「扩展运算符」和for…of进行遍历。
- size 、 set 、 get 、has 、 clear
12.数组方法
map 、 foreach 、 filter 、 sort 、 reverse…
13.js深浅拷贝
- 数据类型分为基本数据类型,引用数据类型
- 前者存储在栈内存中
- 后者地址存储在栈内存中,其值存在堆内存中
- 深浅拷贝
- 浅拷贝
slice()、concat()
:会在栈中开辟另一块空间,并将被拷贝对象的栈内存数据完全拷贝到该块空间中。引用类型的值就是拷贝了地址 - 深拷贝
JSON.parse(JSON.stringify(obj))
:引用类型会在内存中在开辟出来另一块空间存储引用类型的值
- 浅拷贝
14.异步编程的实现方式
- 回调函数
- 优点:简单,容易理解
- 缺点:回调地狱,代码耦合度高
- promise对象
- 优点:利用
then
方法,进行链式书写;可以书写异步错误时的回调函数
- 优点:利用
- async 函数
- 优点:内置执行器,更好的语义,更广适用性,返回的是promise,结构清晰
- 缺点:没有错误处理机制
async/await 是ES7提出的基于Promise的解决异步的最终方案
async是一个加在函数前的修饰符,对async函数可以直接then
await 也是一个修饰符,只能放在async定义的函数内。可以理解为等待。取到值后语句才会往下执行;
// 使用async/await获取成功的结果
// 定义一个异步函数,3秒后才能获取到值(类似操作数据库)
function getSomeThing(){
return new Promise((resolve,reject)=>{
setTimeout(()=>{
resolve('获取成功')
},3000)
})
}
async function test(){
let a = await getSomeThing();
console.log(a)
}
test(); // 3秒后输出:获取成功
15.面向对象的编程思想
- 基本思想就是:使用对象、类、继承、封装等基本概念来完成程序设计
- 优点:以维护、易拓展、降低工作量
16.项目性能优化
- 减少HTTP请求数
- 减少DNS查询:通过DNS将网址转化为IP地址进行访问
- 减少DOM元素数量和操作
- 使用外部的
JS
和CSS
17.单线程,单线程和异步的关系
单线程:一个线程,同一时间只能运行一个程序
异步:非阻塞机制,在某一进程存在阻塞和延迟状况时,不影响主进程的运行。
- JS的单线程是负责JS解释执行的·引擎主线程·是单线程的(单线程和异步不可能同时成为一个语言的特点)。但是JS所依赖的宿主环境(浏览器,node等)是多线程的。这就使得JS拥有了异步属性。
- 浏览器同一时间内只有一个线程在运行JS程序,但是浏览器为耗时程序开辟了另外的线程。
18.说说负载均衡
- 负载均衡是高并发,高可用系统必不可少的关键组件,目标是:尽力将网络流量平均分发到多个服务器上,可以提高系统的整体响应速度和可用性。
- 分类:硬件负载均衡、软件负载均衡
19.作用域链
各个作用域的嵌套关系组成一条作用域链,我们能通过作用域链访问到父级里声明的变量或者函数
20.原型,原型链,继承
- 原型:一个函数可以看做一个类,原型是所有类都有的一个属性。作用就是给这个类的每一个对象都添加统一的方法。
- 分类
- 显示原型:prototype,是每个函数function独有的属性
- 隐式原型:proto,是每个对象都具有的属性
- 分类
- 原型链:A.proto -> B.prototype / B.proto -> C.prototype / C.proto -> …
直到找到顶层对象的显性原型,这个查找路径就是原型链。
//这是一个构造函数
function Foo(name,age){
this.name=name;
this.age=age;
}
/*所有的函数都有一个prototype属性,这个属性是一个对象
所有的对象可以自由扩展属性
于是就有了以下写法*/
Foo.prototype={
// prototype对象里面又有其他的属性
showName:function(){
console.log("I'm "+this.name);//this是什么要看执行的时候谁调用了这个函数
},
showAge:function(){
console.log("And I'm "+this.age);//this是什么要看执行的时候谁调用了这个函数
}
}
var fn=new Foo('小明',19)
/*当试图得到一个对象的属性时,如果这个对象本身不存在这个属性,那么就会去它构造函数的'prototype'属性中去找*/
fn.showName(); //I'm 小明
fn.showAge(); //And I'm 19
- 优点节省资源,不用在每一个对象里面都有
showName()
和showAge()
,原型实现的话,每个对象都可以使用原型中的方法。
21.JS垃圾回收机制
js的垃圾回收机制是为了防止内存泄漏(已经不需要的某一块内存还一直存在着)
垃圾回收机制就是不停歇的寻找这些不再使用的变量,并且释放掉它所指向的内存。
在JS中,JS的执行环境会负责管理代码执行过程中使用的内存。
- 垃圾回收方式:1. 标记清除——大部分浏览器使用,声明是标记,离开环境时再标记,之后将其清除 、 2. 引用计数——会引起内存泄露
22.变量生命周期
- 局部变量:当前函数结束后就会释放内存
- 全局变量:知道浏览器关闭,释放内存
23.逐渐增强和优雅降级
- 低版本->高版本
- 高版本->低版本
纯题:
- js数据类型
- js变量,函数声明 的提升
- 闭包
- == 和 === 之间的区别
- this
- forEach 和 map
- 箭头函数 , 普通函数的区别
- 同源策略
- 如何解决跨域
- 严格模式
- ES6新增
- 数组方法
- js深浅拷贝
- 异步编程的实现方法
- 面向对象的编程思想
- 项目性能的优化
- 单线程,单线程和异步的关系
- 负载均衡
- 作用域链
- 原型、原型链、继承
- JS垃圾回收机制
- 变量生命周期
- 逐渐增强和优雅降级