前端面试题整理 - JavaScript 篇

1. 防抖与节流的区别

防抖(debounce): 当连续触发事件时,如果触发间隔小于设定的时间,那么只会执行最后一次触发时的事件处理函数,每次触发事件重新开始计时。
原理是维护一个计时器,规定在 delay 时间后触发函数,但是在 delay 时间内再次触发的话,就会取消之前的计时器而重新设置。这样一来,只有最后一次操作能被触发。

节流(throttle): 当连续触发事件时,一定时间内只会执行一次函数,执行函数后重新开始计时。(当第一次触发事件时马上执行事件处理函数,最后一次触发事件后也还会执行一次事件处理函数)。
原理是通过时间戳,判断是否到达一定时间来触发函数。

2. V8 垃圾回收

v8 具有内存限制(为了减少对应用性能的影响),在64位系统下位1.4G。

2.1. v8 的垃圾回收策略

  • v8 的内存结构主要分为新生代和老生代,大多数对象开始都会被分配在新生代,存活一段时间后会被转移到老生代。
  • 新生代内存较小但垃圾回收频繁,采用以空间换时间的 Scavenge 算法。
  • 老生代使用标记清除与标记整理算法进行垃圾回收。

3. 如何避免内存泄漏

  • 尽量避免使用全局变量
  • 手动清除定时器
  • 手动清除事件监听器
  • 手动清除 DOM 引用
  • 少用闭包
  • 使用弱引用(ES6的WeakMap和WeakSet)

WeakMap示例:

// 一个对象被多次引用时,例如作为另一对象的键、值或子元素时,将该对象引用设置为 null 时,该对象是不会被回收的,依然存在
    let a = {};
    let arr = [a];

    a = null;
    console.log(arr); // [{}]  {}不会被回收

    let a = {}; 
    let map = new WeakMap();
    map.set(a, '111')
    map.get(a)

    a = null; 

4. 事件循环(Event Loop)

4.1. 浏览器环境

  1. js 引擎遇到异步事件时,将异步事件挂起,继续执行执行栈中的其他任务。
  2. 异步事件返回结果后,将异步事件加入到对应的事件队列中。
  3. 执行栈中的所有任务都执行完毕,主线程空闲时,将微任务事件队列中的事件取出,并将对应的回调依次放入执行栈中执行。
  4. 执行完毕微任务队列中的所有事件后,再依次取出宏任务队列中的事件放入执行栈中执行。

宏任务: DOM 渲染后触发,如 setTimeout,setInterval,Ajax,DOM 事件。
微任务: DOM 渲染前触发,如 Promise.then,queueMicrotask、MutationObserver(监听 DOM 节点变化)

4.2. node 环境

 ┌───────────────────────┐
┌─>│        timers         │
│  └──────────┬────────────┘
│  ┌──────────┴────────────┐
│  │     I/O callbacks     │
│  └──────────┬────────────┘
│  ┌──────────┴────────────┐
│  │     idle, prepare     │
│  └──────────┬────────────┘      ┌───────────────┐
│  ┌──────────┴────────────┐      │   incoming:   │
│  │         poll          │<──connections───     │
│  └──────────┬────────────┘      │   data, etc.  │
│  ┌──────────┴────────────┐      └───────────────┘
│  │        check          │
│  └──────────┬────────────┘
│  ┌──────────┴────────────┐
└──┤    close callbacks    │
   └───────────────────────┘
  1. 首先进入 poll 阶段,执行 poll queue 中的事件,直到所有回调执行完毕或超过 node 执行数限制。
  2. 检查执行 setImmediate() 的 callback,同时将到期的 timer 加入到 timer queue 中,并执行回调。(这两者的顺序是不固定的,受代码运行环境的影响)
  3. 当 I/O 事件返回,进入 I/O callback 阶段并立即执行这个事件的 callback。

setImmediate() 和 setTimeout() 的执行顺序是不固定的,但在一个 I/O 事件的回调中,setImmediate() 永远在 setTimeout() 前执行。

5. EventTarget.addEventListener()

将指定的监听器注册到 EventTarget 上,当该对象触发指定的事件时执行回调。事件目标可以是Element、Document 和 Window 或者其他任何支持事件的对象(XMLhttpRequest)。

// 接口协议
target.addEventListener(type, listener, options);
target.addEventListener(type, listener, useCapture);

type type = string; // 事件的类型(不带 on)
type listener = Function; // 监听到事件后的处理函数,接受 Event 对象作为参数

/**
 * 默认值为 false
 * 监听器在监听时有三个阶段,
 * 捕获阶段(根节点到子节点) -> 目标阶段(目标本身) -> 冒泡阶段(目标本身到根节点),
 * useCapture 设置为 true 时监听器将在捕获阶段处理事件,设置为 false,则在目标或冒泡阶段处理事件。
 */
type useCapture = boolean;

type options = {
  capture: boolean; // 默认值为 false,规定事件在捕获阶段处理还是冒泡阶段处理,同上。
  once: boolean; // 默认值为 false,表示监听器只会调用一次,之后自动移除
  passive: boolean; // 默认值为 false,设置为true时,监听器永远不会调用 preventDefault()。
};
// 使用 passive 改善滚屏性能,在添加 touchmove 事件监听器时,设置 passive 为 true,防止监听器阻塞页面滚动

6. typeof

  1. 除 function 外的所有引用类型使用 typeof 得到的结果均为 ‘object’。
  2. null 使用 typeof 得到的结果为 ‘object’。

基本数据类型:number, string, boolean, undefined, symbol( es6 新增,typeof Symbol() === ‘symbol’ )。
引用数据类型:object, function。

7. instanceof

语法: object instanceof constructor
用来检测 constructor.prototype 是否在参数 object 的原型链上。

Object instanceof Function === true
Function instanceof Object === true
Object instanceof Object === true
Function instanceof Function === true

8. Object.prototype.toString.call()

由于引用类型通过 typeof 只能得到 ‘object’ 和 ‘function’ 两种结果,无法区分 object、array、date、math 等类型,因此当不确定变量类型时,可用 Object.prototype.toString.call() 方法来识别。
Object.prototype.toString() 方法会返回一个形如 ‘[object XXX]’ 的字符串。

Object.prototype.toString.call({}) === '[object Object]'
Object.prototype.toString.call([]) === '[object Array]'
Object.prototype.toString.call(null) === '[object Null]'
Object.prototype.toString.call(new Date()) === '[object Date]'

9. 原型链

每一个 JavaScript 对象(null 除外)在创建的时候就会与之关联另一个对象,这个对象就是我们所说的原型,每一个对象都会从原型"继承"属性。
原型链最终指向 null(Object.prototype.proto === null),null 没有原型对象。
原型链

9.1. 实现继承的方式

寄生组合式继承

function Parent() {
    this.name = 'parent';
}
function Child() {
    Parent.call(this);
}
Child.prototype = Object.create(Parent.prototype) // 或者 Child.prototype = new Parent();
Child.prototype.constructor = Child;

// Object.create(proto: 原型对象)  创建一个以传入的 proto 对象为原型的新对象

10. Promise

10.1. Promise 状态

Promise 必须为以下三种状态之一:等待态(Pending)、执行态(Fulfilled)、拒绝态(Rejected)。一旦 Promise 被 resolve 或 reject,不能再迁移至其他任何状态。

10.2. Promise 基本执行过程

  1. 初始化 Promise 状态(Pending)
  2. 立即执行 Promise 中传入的 fn 函数(参数为 resolve, reject)
  3. 执行 then(…)注册回调

注意:then 方法传入的参数 onFulfilled,必须在 then 方法被调用的那一轮事件循环之后的新执行栈中执行。
promise 内的函数依旧是宏任务,只有 .then() 方法内注册的回调函数才是微任务。
resolve 或 reject 的值即为 .then() 和 .catch() 中的参数。

11. 作用域和执行上下文

11.1. 作用域

js 是词法作用域,变量的访问范围仅由 声明 时候的区域决定。

var count = 10;
function a() {
  return count + 10;
}
function b() {
  var count = 20;
  return a();
}
console.log(b());
// 结果是20
// 因为js是词法作用域,a方法中的count向外查找,外层是全局,count为10,因此+10之后结果是20。

var foo = 3;
function func() {
  var foo = foo || 5;
  console.log(foo);
}
func();
// 结果是5
// var foo = foo || 5; 这一行可以拆分为两步:var foo; foo = foo || 5; 
// 此时 foo 为 undefined,因此赋值后 foo 为 5,打印 5。
// 如果这一行去掉 var,即 foo = foo || 5,那么 foo 会向外查找,即打印 3。

11.2. 执行上下文

  • 每个上下文都有一个关联的变量对象 VO(variable object),而这个上下文中定义的所有变量和函数都在这个对象上。
  • 上下文中的代码在执行的时候,会创建变量对象的一个作用域链
  • 如果上下文是函数,则其活动对象 AO 用作变量对象,活动对象最初只有一个定义变量:arguments。

12. 闭包

12.1. 概念

闭包是指那些引用了另一个函数作用域中变量的函数。
通常在嵌套函数中实现,原因是内部函数的作用域链包含外部函数的作用域链(即能够访问外部函数的活动对象)。
注意:自由变量的查找,是在函数定义的地方,向上级作用域查找。不是在执行的地方。

// 函数作为参数传递
function print(fn) {
  const a = 200;
  fn();
}

const a = 100;
function fn() {
  console.log(a);
}

print(fn); // 100
// 函数作为返回值返回
function create() {
  const a = 100;

  return function () {
    console.log(a);
  };
}

const fn = create();
const a = 200;
fn(); // 100

12.2. 使用场景

  1. 实现私有属性,getter 和 setter 方法。
  2. 迭代器(每次执行函数返回下一个值)。
  3. 缓存(复杂的计算操作,将对应入参的计算结果缓存下来,再次执行函数时,如果缓存有,就直接从缓存中取,如果没有,再进行计算)。

13. 函数

13.1. arguments对象

  • 只有用 function 定义函数时才会存在,包含调用函数时传入的所有参数。
  • arguments 对象是一个类数组,具有 Interator 接口。

13.2. this 指向

  • 在标准函数中,this 指向函数 执行时 的上下文对象。
  • 在箭头函数中,this 指向函数 定义时 的上下文对象。

14. new 操作符实现过程

  1. 创建一个对象(new Object())
  2. 将构造函数的 prototype 赋值给已创建对象的 proto 属性
  3. 执行构造函数,使用 apply() 方法将 this 指向已创建的对象,并传入构造函数的 arguments 对象
  4. 获取执行构造函数后的返回值,若返回值类型为’object’,则 new 操作符返回构造函数返回值,否则返回已创建的对象

15. 柯里化(Currying) 和 Compose 函数

15.1 Currying

把接收多个参数的函数变换成接受一个参数的函数,并返回新的函数来接收剩下的参数,新的函数返回结果。

add(1, 2, 3); // 普通函数
currying(1)(2)(3); // Currying 后

使用场景: 参数复用,提前确认,延迟运行(多次调用,第一步相同,第二步不同)
bind 方法的实现原理就是 Currying。

// 支持多参数传递
function progressCurrying(fn, args) {
    const _this = this
    const len = fn.length;
    const args = args || [];

    return function() {
        const _args = Array.prototype.slice.call(arguments);
        Array.prototype.push.apply(args, _args);

        // 如果收集的参数个数小于最初的fn.length,则递归调用,继续收集参数
        if (_args.length < len) {
            return progressCurrying.call(_this, fn, _args);
        }

        // 参数收集完毕,则执行fn
        return fn.apply(this, _args);
    }
}

15.2 compose 函数

compose 函数就是将层级嵌套函数扁平化。

fn3(fn2(fn1(1,2,3))) // 普通函数
compose(fn1, fn2, fn3)(1,2,3) // compose后

实现方式

const compose = function(...args) {
  let init = args.pop()
  return function(...arg) {
    return args.reverse().reduce(function(sequence, func) {
      return sequence.then(function(result) {
        return func.call(null, result)
      })
    }, Promise.resolve(init.apply(null, arg)))
  }
}

16. ES6 注意点

  1. 任何定义了迭代器(Iterator)接口的对象(比如 arguments),都可以使用 for…of 循环。
  2. WeakSet 和 WeakMap 的元素和键只能为对象,它们都是弱引用,垃圾回收机制不会将该引用考虑在内。
  3. Symbol 类型表示一个独一无二的值(let s = Symbol()),相同参数的 Symbol 函数返回值依然不相等。
  4. Array 方法:
    • Array.flat(n) 扁平化
    • Array.reduce((prev, cur) => any) 循环
    • Array.fill(value, start = 0, end = array.length) 填充数据,会修改原数组
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值