前端面试题,JavaScript进阶篇共收录面试题28道。
1、说说ECMAScript6 怎么写 calss
- ES6的class可以看作是一个语法糖,它的绝大部分功能ES5都可以做到,新的class写法只是让对象原型的写法更加清晰、更像面向对象编程的语法
// 定义类
class Point {
constructor(x, y) {
// 构造方法
this.x = x; // this关键字代表实例对象
this.y = y;
} toSting() {
return '(' + this.x + ',' + this.y + ')';
}
}2、说说什么是面向对象编程及面向过程编程,它们的异同和优缺点
- 面向过程就是分析出解决问题所需要的步骤,然后用函数把这些步骤一步一步实现,使用的时候一个一个依次调用就可以了
- 面向对象是把构成问题事务分解为各个对象,建立对象的目的不是为了完成一个步骤,而是为了描述某个事物在整个解决问题的步骤中的行为
- 面向对象是以功能来划分问题,而不是步骤
3、说说异步编程的实现方式
- 回调函数
- 优点:简单、容易理解
- 缺点:不利于维护,代码耦合高
- 事件监听(采用时间驱动模式,取决于某个事件是否发生):
- 优点:容易理解,可以绑定多个事件,每个事件可以指定多个回调函数
- 缺点:事件驱动型,流程不够清晰
- 发布/订阅(观察者模式)
- 类似于事件监听,但是可以通过“消息中心”,了解现在有多少发布者,多少订阅者
- Promise 对象
- 优点:可以利用then方法,进行链式写法;可以书写错误时的回调函数
- 缺点:编写和理解,相对比较难
- Generator 函数
- 优点:函数体内外的数据交换、错误处理机制
- 缺点:流程管理不方便
- async 函数
- 优点:内置执行器、更好的语义、更广的适用性、返回的是Promise、结构清晰
- 缺点:错误处理机制
4、说说面向对象编程思想
- 基本思想是使用对象、类、继承、封装等基本概念来进行程序设计
- 优点:易维护;易扩展;开发工作的重用性、继承性高,降低重复工作量;错短了开发周期;
5、说说Gulp是什么
- Gulp是前端在开发过程中一种基于流的代码构建工具,是自动化项目的构建利器;它不仅能对网站资源进行优化,而且在开发过程中很多重复的任务能够使用正确的工具自动完成
- Gulp的核心概念:流
- 流:就是建立在面向对象基础上的一种抽象的处理数据的工具。在流中,定义了一些处理数据的基本操作,如读取数据,写入数据等。程序员是对流进行数据操作的,而不用关心流的另一头数据的真正流向。
- Gulp正式通过流和代码优于配置的策略来尽量简化任务编写的工作。
6、想实现一个对页面某个节点的拖拽,如何做?
- 给需要拖拽的节点绑定 mousedown,mousemove,mouseup事件
- mousedown事件触发后,开始拖拽
- mousemove时,需要通过 event.clientX 和 clientY 获取拖拽位置,并实时更新位置
- mouseup时,拖拽结束
- 需要注意浏览器边界的情况
7、封装一个函数,参数是定时器的时间,then执行回调函数
function sleep(time) {
return new Promise((resolve) => setTimeout(resolve, time));
}8、怎么判断两个对象的相等
var obj = { a: 1, b: 2 };
var obj2 = { a: 1, b: 2 };
var obj3 = { a: 1, b: "2" };
console.log(JSON.stringify(obj) == JSON.stringify(obj2)); // true
console.log(JSON.stringify(obj) == JSON.stringify(obj3)); // false9、简单说一下性能优化有哪些
- 减少HTTP请求数
- 减少DNS查询
- 使用CDN
- 避免重定向
- 图片懒加载
- 减少DOM元素数量
- 减少DOM操作
- 使用外部JavaScript和CSS
- 压缩JavaScript、CSS、字体和图片
- 优化CSS sprite
- 使用iconfont
- 字体裁剪
- 多域名分发划分内容到不同域名
- 尽量减少iframe的使用
- 避免图片src为空
- 把样式表放在link中
- 把JavaScript放在页面底部
10、说说你对Electron的理解
- 减少HTTP请求数最最重要的一点,electron实际上是一个套了Chrome的nodeJS程序
- 所以从两个方面来说
- Chrome,无各种兼容性问题
- NodeJS,NodeJS能做的electron都能做
11、说说对WebSocket的理解
- 历史来源:由于http存在一个明显的弊端:消息只能由客户端推送到服务器端,而服务器端不能主动推送到客户端。导致如果服务器有连续的变化,这时只能使用轮询,二轮询效率过低,并不合适,这个时候WebSocket被发明出来
- 支持双向通信,实时性更强
- 可以发送文本,也可以是二进制文件
- 协议标识符是ws,加密后是wss
- 较小的控制开销。连接后创建ws客户端、服务端进行数据交互时,协议控制的数据包头部较小。在不包含头部的情况下,服务端到客户端的包头只有2-10个字节(取决于数据包长度),客户端到服务端的话,需要加上额外的4字节掩码。而HTTP协议每次通信都需要携带完整的头部
- 支持扩展。ws协议定义了扩展,用户可以扩展协议,或者实现自定义的子协议。(比如自定义压缩算法)
- 无跨域问题
12、什么是单线程,和异步的关系?
- 单线程:只有一个线程,只能做一件事情
- 原因:避免DOM渲染冲突
- 浏览器需要渲染DOM
- JS可以修改DOM结构
- JS执行的时候,浏览器DOM渲染会暂停
- 两段JS也不能同时执行,都修改DOM的话就冲突了
- webworker 支持多线程,但不能访问DOM
- 解决方案:使用异步
13、说说负载均衡
- 单台服务器共同协作,不让其中某一台或几台超额工作,发挥服务器的最大作用
- http重定向负载均衡:调度者根据策略选择服务区以302相应请求,缺点只有第一次有效果,后续操作维持在该服务器
- DNS负载均衡:解析域名时,访问多个IP服务器中的一个(可监控性较弱)。原因:避免DOM渲染的冲突
- 反向代理负载均衡:访问统一的服务器,有服务器进行调度访问实际的某个服务器,对统一的服务器要求大,性能收到服务器群数量的影响
14、webpack的一些plugin,怎么使用webpack对项目进行优化
- 构建优化
- 减少编译体积 ContextReplacementPugin、IgnorePlugin、babel-plugin-import、babel-plugin-transform-runtime。
- 并行编译 happypack、thread-loader、uglifyjsWebpackPlugin开启并行
- 缓存 cache-loader、hard-source-webpack-plugin、uglifyjsWebpackPlugin开启缓存、babel-loader开启缓存
- 预编译 dllWebpackPlugin && DllReferencePlugin、auto-dll-webapck-plugin
- 性能优化
- 减少编译体积 Tree-shaking、Scope Hositing。
- hash缓存 webpack-md5-plugin
15、编写一个loader
- loader就是一个node模块,它输出了一个函数。当某种资源需要用这个loader转换时,这个函数就会被调用。并且这个函数可以通过提供给它的this上下文访问Loader API。
module.exports = {
module: {
rules: [
{ test: /\.css$/, use: 'css-loader' },
{ test: /\.ts$/, use: 'ts-loader'}
]
}
}16、webpack打包体积,优化思路?
- 提取第三方库或通过引用外部文件的方式引入第三方库
- 代码压缩插件 UglifyJsPlugin
- 服务器启用gzip压缩
- 按需加载资源文件 require.ensure
- 优化devtool中的source-map
- 剥离css文件,单独打包
- 去除不必要插件,通常就是开发环境与生产环境用同一套配置文件导致
17、webpack打包效率
- 开发环境采用增量构建,启用热更新
- 开发环境不做无意义的工作,如提取css计算文件hash等
- 配置devtool
- 选择合适的loader
- 个别loader开启cache 如babel-loader
- 第三方库采用引入方式
- 提取公共代码
- 优化构建时的搜索路径,指明需要构建目录及不需要构建目录
- 模块化引入需要的部分
18、原型 / 构造函数 / 实例
- 原型(prototype),一个简单的对象,用于实现对象的属性继承。可以简单的理解成对象的父。在Firefox 和 Chrome中,每个JavaScript对象中都包含一个__proto__的属性指向父(对象的原型),可以用obj__proto__访问
- 构造函数:可以通过new来新建一个对象的函数
- 实例:通过构造函数和new创建出来的对象,便是实例。实例通过__proto__指向原型,通过constructor指向构造函数
- 以Object为例,我们常用的Object便是一个构造函数,因此我们可以通过它构建实例:
const instance = new Object();- 此时,实例为instance,构造函数为Object,我们知道构造函数拥有一个prototype的属性指向原型,因此原型为:
const prototype = Object.prototype;- 原型 / 构造函数 / 实例三者的关系
- 实例 .__proto__ === 原型
- 原型 .constructor === 构造函数
- 构造函数 .prototype === 原型
// const o = new Object();
// o.constructor === Object --> true
// o.__proto__ = null;
// o.constructor === Object --> false
// 实例.constructor === 构造函数19、讲一讲原型链
- 原型链是由原型对象组成,每个对象都有__proto__属性,指向了创建该对象的构造函数的原型,__proto__将对象连接起来组成了原型链。是一个用来实现继承和共享属性的有限的对象链。
- 属性查找机制: 当查找对象的属性时,如果实例对象自身不存在该属性,则沿着原型链往上 一级查找,找到时则输出,不存在时,则继续沿着原型链往上一级查找,直至最顶级的原型 对象 Object.prototype ,如还是没找到,则输出 undefined。
- 属性修改机制: 只会修改实例对象本身的属性,如果不存在,则进行添加该属性,如果需要
修改原型的属性时,则可以用: b.prototype.x = 2 ;但是这样会造成所有继承于该对象的
实例的属性发生改变。
20、作用域链
我们知道,我们可以在执行上下文中访问到父级甚至全局的变量,这便是作用域链的功
劳。作用域链可以理解为一组对象列表,包含 父级和自身的变量对象,因此我们便能通过作用
域链访问到父级里声明的变量或者函数。
有两部分组成:
- [[scope]] 属性: 指向父级变量对象和作用域链,也就是包含了父级的[[scope]] 和 AO。
- AO : 自身活动对象。
如此[[scopr]]包含[[scope]],便自上而下形成一条 链式作用域。
21、对象的拷贝
- 浅拷贝: 以赋值的形式拷贝引用对象,仍指向同一个地址,修改时原对象也会受到影响
- Object.assign
- 展开运算符( ... )
- 深拷贝: 完全拷贝一个新对象,修改时原对象不再受到任何影响
- JSON.parse(JSON.stringify(obj)) : 性能最快,但是具有循环引用的对象时,报错。当值为函数、 undefined 、或 symbol 时,无法拷贝
- 递归进行逐一赋值
22、new 运算符的执行过程
- 新生成一个对象
- 连接到原型:obj.__proto__ = Con.prototype
- 绑定 this:apply
- 返回新对象(如果构造函数有自己 return 时,则返回该值)
23、instanceof原理
- 在实例的原型对象链中找到该构造函数的prototype属性所指向的原型对象,就返回true
24、类型判断
- 判断Target的类型,单单用typeof并无法完全满足,这其实并不是bug,本质原因是JS的万物皆对象的理论。因此要真正完美判断时,我们需要区分对待
- 基本类型null:使用String(null)
- 基本类型String、number、boolean、undefined、function:直接使用typeof即可
- 其余引用类型Array、Date、RegExp、Error:调用toString后根据[object xxx]进行判断
25、模块化
- 模块化开发在现代开发中已是必不可少的一部分,它大大提高了项目的可维护、可扩展和可协作性。通常,我们在浏览器中使用ES6的模块化支持,在Node中使用CommonJS的模块化支持
- 分类:
- ES6:import / export
- CommonJS:require / module.exports / exports
- amd: require / defined
- require 和 import的区别
- require支持动态导入,import不支持。
- require是同步导入,import是异步导入
- require是值拷贝,导出值变化不会影响导入值;import指向内存地址,导入值会随导出值而变化
26、防抖与节流
- 防抖与节流函数是一种常用的高频触发优化方式,能对性能有较大的帮助
- 防抖:将多次高频操作优化为只在最后一次执行,通常的使用场景是:用户数据,只需要再输入完成后做一次输入校验即可
- 节流:每隔一段时间后执行一次,也就是降低频率,将高频操作优化为低频操作,通常使用场景:滚动条时间或者resize事件,通常每隔100-500毫秒执行一次即可
27、函数执行改变this
- 由于JS的设计原理:在函数中,可以引用运行环境中的变量。因此就需要一个机制来让我们可以在函数体内部获取当前的运行环境,这便是this
- obj.fn(),便是obj调用了函数,即函数中的this===obj
- fn(),这里看成window.fn(),因此 this === window
- 三种方式可以手动修改this的指向
- call:fn.call(target,1,2)
- apply:fn.apply(target, [1,2])
- bind: fn.bind(target)(1,2)
28、说说你对Promise的了解
- 依照Promise/A+的定义,Promise有四种状态:
- pending:初始状态
- fulfilled:成功的操作
- rejected:失败的操作
- settled:Promise 已被 fulfilled 或 rejected,且不是pending
- 另外,fulfilled 以 rejected一起合成settled
- Promise对象用来进行延时和异步计算
- Promise的构造函数
- 构建一个Promise,最基本的用法如下:
var promise = new Pormise(function (resolve, reject) {
if (...) {
resolve(result);
} else {
reject(Error(errMessage))
}
})- Promise实例拥有then方法(具有then方法的对象,通常被称为thenable)。它的使用方法如下:
promise.then(onFulfilled, onRejucted);有错请指正,侵删
查看全部面试题
















