前端核心面试题:JS基础

554 阅读6分钟

JS基础

什么是事件委托

  • 概念:子元素委托事件给父元素去处理;
  • 好处:节约内存+方便管理+防内存泄漏;
  • 节约内存:事件监听器的数量少;
  • 方便管理:无需考虑子元素动态增减时事件监听器的动态增减问题;
  • 防内存泄露:子元素被删除时无需删除其对应的事件监听器;
  • PS:追问=哪些情形会造成内存泄露?

ES6有哪些新特性?

变量层面

  • let:var弊病=变量提升+重复声明+作用域穿透;
  • const:通常将不希望修改地址的对象声明为常量;

数据结构层面

  • 数组:解构+展开;
  • 对象:解构+展开+简写;
  • 字符串:模板字符串;
  • Map:方便增删改产的键值对存储器;
  • Set: 有序且不重复的元素集;

函数层面

  • 箭头函数:this从父级作用域继承;
  • PS:this的可能情形;

类层面

  • class系列语法:class(构造函数+原型属性) + constructor(构造函数) + static静态成员 + super调用父类方法;super(name)调用父类构造器; super.xxx()调用父类实例方法;

什么是深拷贝/浅拷贝

  • 浅拷贝:引用数据类型赋值给别人(包括调用函数时实参给形参赋值)= 地址被拷贝 = 修改任何副本都会影响其它副本;
  • 浅拷贝示例 const obj2 = objfn(obj)
  • 深拷贝:将对象中的所有key-value递归拷贝给另外一个对象,递归到每一个叶子结点(即value是基本数据类型或函数),深拷贝的副本修改相互不影响;

什么是值传递/引用传递

  • 值传递:基本数据类型赋值给别人,包括调用函数时作为入参传递;
  • 引用传递:引用数据类型赋值给别人,包括调用函数时作为入参传递;
  • 值传递示例 let num2 = numfn(num)
  • 引用传递示例 let obj2 = objfn(obj)

谈谈对事件传播/派发机制的理解

  • 解决事件发生时具体由谁消费的问题;
  • 具体解决方案可从三方面入手,即:指定传播方向 + 按需设置一级到多级监听器 + 设置传播断点;
  • 指定传播方向:dom.addEventListener(type,handler,useCapture)最后一个入参true即使用捕获,否则使用冒泡,默认冒泡;
  • 设置断点:e.stopPropagation()

内存泄露的可能情形有哪些?

  • 用完的事件监听器:DOM元素删除时忘记删除其事件监听器;
  • 用完的事件监听器:离开页面时没有释放各种事件监听器;(div.onclick=null,div.removeEventListener...)
  • 用完的定时器:定时器用完没有clear;
  • 用完的全局变量:用完没释放的全局变量(obj=null)
  • 用完的闭包:用完未释放的闭包(innerFn = null)
  • 总结:【用毕未释放】的【事件监听器】+【定时器】+【全局变量】+【闭包】;

谈谈对闭包的理解

  • 广义闭包:凡返回引用数据的函数,在其返回值被引用期间,其执行空间无法被释放,客观上形成一个临时存在的封闭作用域,即函数闭包;
  • 狭义闭包:即返回函数的函数,外层函数通常作为内层函数的状态缓存区;
  • 函数式编程:闭包是函数式编程的基础,可以派生出诸多玩法(这里可以随便举几个案例)
  • 释放闭包:闭包会导致算法的时间和空间开销增大,用完后一定要释放,否则会形成内存泄露;
  • 释放方式:let ret = enclosure(); 用完后ret=null,无人引用闭包的返回值时闭包就能释放了;

深拷贝 VS 浅拷贝

  • 浅拷贝:复制地址+相互影响;const objCopy = obj
  • 深拷贝:递归拷贝对象的所有key-value + 直到叶子结点 + 互不影响;const objCopy = deepCopy(obj)

值传递 VS 引用传递

  • 值传递:基本数据类型(number,string,boolean,null,undefined) + 赋值给别人 + 值拷贝 + 互不影响;const numCopy = num; fn(num)基本数据类型的实参传递给形参走的是值传递/值拷贝;
  • 引用传递:引用数据类型 + 赋值给别人(包括实参给形参赋值)+ 地址的浅拷贝 + 相互影响 const objCopy = obj fn(obj)

前端有哪些数据存储方式

  • cookie: 4K级别 + 键值存储 + 主要用于前后端共享用户信息;
  • localStorage/sessionStorage: 50M级别 + 键值存储 + 前端独享 + 缓存业务数据(以减少网络请求次数);

@面向对象

this的所有可能情形

  • 普通函数的this就是调用者;(显式主语+无主语时为window+DOM事件监听器的主语恒为事件源e.currentTarget)
  • 箭头函数的this从父级作用域继承;
  • 普通函数可以通过 fn.call/apply/bind 绑定this
  • 构造器里的this为正在构建的实例
  • 实例方法中的this为当前实例
  • 静态方法中的this为当前类

typeof 与 instanceof 区别

  • typeof主要用于判断基本数据类型,对于引用数据类型只能识别为funciton或object
  • instanceof判断某个对象是否是某类的实例或后代实例
  • zhangsan instanceof Person或(Animal,Object)结果都是true

JS如何实现继承

  • 以父类实例作为子类原型

new操作符具体干了什么

const zhangsan = new Person('张三',18)

  • 提前构造一个默认的返回值p
  • 将构造方法Person中的this绑定为这个p对象
  • 最终将这个p对象返回

Map与Object的区别

  • 本质上都是key-value管理数据
  • Map是ES6新增的数据结构,主要就是在Object的基础上封装除了几个便捷API,以做到和其它语言接轨(很多其它语言中都有Map这种数据结构)
  • 这些便捷API包括map.set(key,value),let value = map.get(key),map.delete(key),map.size
  • 一个简单的MyMap封装

class MyMap {
    constructor(){
        this.dataObj = {}
        this.size = 0
    }

    set(key,value){
        if(!this.dataObj.hasOwnProperty(key)){
            this.size ++
        }
        this.dataObj[key] = value
    }

    get(key){
        return this.dataObj[key]
    }

    delete(key){
        delete this.dataObj[key]
        this.size --
    }
}

const mm = new MyMap()
mm.set("name","OOP小菜鸡")
console.log(mm.get("name"));
console.log(mm);

@异步

Promise与asycn,await的区别

  • await是promise.then的语法糖
//ajaxApi(url,parmas)的返回值是promise对象
//阻塞等待promise对象resolve数据出来
const data = await ajaxApi(url,parmas)
  • await关键字所在的函数必须声明为异步函数
async function fn(){//内含await}
async ()=>{//内含await}
const fn = async ()=>{//内含await}
  • promise.catch由await所在的代码块进行try-catch
async fn(){
    try{
        // ajaxApi(url,parmas)的返回值是promise对象
        // 如果promise毁约(reject),则错误信息由try-catch的catch获得
        const data = await ajaxApi(url,parmas)
    }catch(err){
        //此处的err即promise所reject出的错误信息
        hanleErr(err)
    }
}