# 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
Last Updated: 7/23/2021, 10:10:19 AM