# JavaScript面试题
# 9.ES6 Modules 相对于 CommonJS 的优势是什么?
- CommonJS和ES6 Module都可以对引入的对象进行赋值,即对对象内部属性的值进行改变;
- CommonJS 模块输出的是一个值的拷贝,ES6 模块输出的是值的引用。即ES6 Module只存只读,不能改变其值,具体点就是指针指向不能变;
- CommonJS 模块是运行时加载,ES6 模块是编译时输出接口。
- CommonJS 模块的require()是同步加载模块,ES6 模块的import命令是异步加载,有一个独立的模块依赖的解析阶段。
- import 的接口是 read-only(只读状态),不能修改其变量值。 即不能修改其变量的指针指向,但可以改变变量内部指针指向,可以对 commonJS 对重新赋值(改变指针指向),但是对 ES6 Module 赋值会编译报错。
优势: CommonJS 加载的是一个对象(即module.exports属性),该对象只有在脚本运行完才会生成。而 ES6 Modules不是对象,它的对外接口只是一种静态定义,在代码静态解析阶段就会生成。
# 8.设计一个简单的发布/订阅
class Event{
constructor () {}
//首先定义一个事件容器,用来装事件数组(因为订阅者可以是多个)
handlers = {}
//事件添加方法,参数有事件名和事件方法
addEventListener(type, handler) {
//首先判断handlers内有没有type事件容器,没有则创建一个新数组容器
if(!(type in this.handlers)){
this.handlers[type] = []
}
//将事件存入
this.handlers[type].push(handler)
}
//触发事件两个参数(事件名,参数)
dispatchEvent (type, ...params){
//若没有注册该事件则抛出错误
if(!(type in this.handlers)){
return new Error('未注册该事件')
}
//遍历触发
this.handlers[type].forEach(handler => {
handler(...params)
})
}
//事件移除参数(事件名,删除的事件,若无第二个参数则删除该事件的订阅和发布)
removeEventListener (type, handler) {
//无效事件抛出
if(!(type in this.handlers)){
return new Error('无效事件')
}
if(!handler){
//直接移除
delete this.handlers[type]
}else{
const idx = this.handlers[type].findIndex(ele => ele === handler)
//抛出异常事件
if(idx === undefined){
return new Error('无该绑定事件')
}
//移除事件
this.handlers[type].splice(idx, 1)
if(this.handlers[type].length === 0 ){
delete this.handlers[type]
}
}
}
}
//创建event实例
var event = new Event()
//定义一个自定义事件:‘load’
function load (params) {
console.log('load', params)
}
event.addEventListener('load', load)
//在定义一个load事件
function load2 (params) {
console.log('load2', params)
}
event.addEventListener('load', load2)
//触发该事件
event.dispatchEvent('load', 'load事件触发')
//移除load2事件
event.removeEventListener('load', load2)
//移除所有load事件
event.removeEventListener('load')
# 7. 什么是闭包?
- 闭包的实质是因为函数嵌套而形成的作用域链
比如说:函数 A 内部有一个函数 B,函数 B 可以访问到函数 A 中的变量,那么函数 B 就是闭包
- 用途:使用闭包主要是为了设计私有的方法和变量
- 优点:可以避免变量被全局变量污染
- 缺点:函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包
- 解决方法:在退出函数之前,将不使用的局部变量全部删除
# 6. 手写选择排序和冒泡排序
- 选择排序
let arr = [3, 4, 1, 2];
let len = arr.length;
//这里之所以是len-1,是因为到最后两个元素,交换位置,整个数组就已经排好序了
for(let i = 0; i < len -1; i++){
let min = arr[i];
//j = i +1 是把与自己比较的情况给省略掉
for(let j = i + 1; j < len; j++){
if(arr[j] < min){
//利用es6数组的解构赋值交换数据
[arr[j], min] = [min, arr[j]]
}
}
arr[i] = min;
}
console.log(arr) // [ 1, 2, 3, 4 ]
- 冒泡排序
let arr = [3, 4, 1, 2];
let max = arr.length - 1;
for(let i = 0; i < max; i++){
// 声明一个变量,作为标志位
// 如果某次循环完后,没有任何两数进行交换,就将标志位设置为 true,表示排序完成
let flag = true;
for(let j = 0; j < max - i; j++){
if(arr[j] > arr[j + 1]){
// 利用ES6数组的解构赋值交换数据
[arr[j], arr[j + 1]] = [arr[j + 1], arr[j]];
flag = false;
}
}
if(flag){
break;
}
}
console.log(arr);
# 5.数组去重和反转数组
- 数组去重
方法一
扩展运算符和Set结构相结合,就可以去除数组的重复成员
[...new Set([1, 2, 2, 3, 4, 5, 5])]
// [1, 2, 3, 4, 5]
方法二
Array.from()能把set结构转换为数组
Array.from(new Set([1, 2, 2, 3, 4, 5, 5]))
// [1, 2, 3, 4, 5]
方法三 (ES5)
function arrayToHeavy(arr) {
let temp = [];
arr.forEach(e => {
if(temp.indexOf(e) == -1){
temp.push(e);
}
})
return temp;
}
- 反转数组
要求
输入: I am a student 输出: student a am I
输入是数组 输出也是数组 不允许用 split splice reverse
function reverseArray(arr) {
let result = [];
let len = arr.length - 1;
for (let i = 0; i <= len; i++){
result[i] = arr[len -i];
}
return result;
}
console.log(reverseArray(['I', 'am', 'a', 'student']))
// ["student", "a", "am", "I"]
# 4.函数防抖和节流
- 函数防抖和节流是优化高频率执行js代码的一种手段
- 节流:可以减少高频调用函数的执行次数
- 防抖:可以让被调用的函数在一次连续的高频操作中只被调用一次
作用:减少代码执行次数,提升网页性能
# 3.深拷贝和浅拷贝
深拷贝
- 修改新变量的值不会影响原有变量的值
- 默认情况下基本数据类型都是深拷贝
浅拷贝
- 修改新变量的值会影响原有变量的值
- 默认情况下引用类型都是浅拷贝
# 2.[] == ![]结果是什么?为什么
==中,左右两边都需要转换为数字然后进行比较。 []转换数字为0。 ![]首先是转换为布尔值,由于[]作为一个引用类型转换为布尔值为true,因此![]为false,进而在转换成数字,变为0。 0 == 0,结果为true
# 1.如何让if(a == 1 && a == 2) 条件成立
var a = {
value: 0,
valueOf:function() {
this.value ++
return this.value
}
}
console.log(a == 1 && a == 2) //true