Skip to content
Spotify - 每月低于 10 元

JavaScript 面试题

var & let & const

1.变量提升

  • var 声明的变量存在变量提升,可以在变量声明之前调用,值为 undefined
  • let 和 const 不存在变量提升,他们声明的变量一定要先声明后使用,否则报错

2.暂时性死区

  • var 不存在暂时性死区
  • let 和 const 存在暂时性死区,只有等到声明变量的那行代码出现,才可以获取和使用该变量

3.作用域

  • var 为函数作用域
  • let 和 const 块级作用域

4. 重复声明

  • var 允许重复声明
  • let 和 const 在同一作用域内不允许重复声明

for...in & for...of

  • for...in 遍历得到 key,适用于可枚举的数据(数组、字符串、对象)
  • for...of 遍历得到 value,适用于可迭代的数据(数组、字符串、Set、Map)
  • for await ... of 用于遍历一组 Promise

值类型 & 引用类型

类型

  • 值类型:string、number、boolean、none、undefined、bigInt、symbol
  • 引用类型:Object、Array、Function 等

函数传参

  • 值类型:函数将传入的值复制赋值给对应的参数
  • 引用类型:函数会将其引用地址赋值给对应的参数,在函数内操作参数会影响到原对象

类型检测

  • typeof 用于检测值类型,检测复杂类型都是 object 或 function
  • instanceof 用于检测引用类型

DOM 元素高度

不能使用箭头函数

精度丢失

原因

  • 在 JavaScript 中,采用 IEEE754 规范中64位双精度浮点数编码
  • 对于64位的双精度浮点数,最高的1位是符号位S,11位是指数E,52位为有效数字M。
  • 0.1 + 0.2 转换为二进制后,为无限不循环小数,只保留52位有效数字,就会造成精度丢失

解决方案

  • 第三方库:math.js decimal.js
  • 原生方法:使用 toFixed() 四舍五入
  • 转换为整数后进行运算

输入 URL 的过程

加载过程

  1. DNS 解析:域名 -> IP 地址
  2. 浏览器根据 IP 地址向服务器建立 TCP 连接
  3. 发起 HTTP 请求
  4. 服务器处理 HTTP 请求,并返回给浏览器

渲染过程

  1. 根据 HTML 代码生成 DOM Tree
  2. 根据 CSS 代码生成 CSSOM
  3. 将 DOM Tree 和 CSSOM 整合形成 Render Tree
  4. 根据 Render Tree 渲染页面
  5. 遇到 <script> 则暂停渲染,优先加载并执行 JS 代码
  6. 直至把 Render Tree 渲染完成

加载完毕

  • windows.onload 页面全部资源加载完毕才会执行
  • DOMContentLoaded DOM 渲染完即可执行,此时图片、视频可能还没有加载完

内存泄漏

  • 被全局变量、函数引用,组件销毁时未清除
  • 被全局事件、定时器引用,组件销毁时未清除
  • 被自定义事件引用,组件销毁时未清除

首屏优化

渲染 10 万条数据

垃圾回收

手写系列

浅拷贝 & 深拷贝

浅拷贝

js
// 浅拷贝
function shallowClone(obj) {
  let result = {};
  // 遍历对象的每一个成员,一项一项拷贝给新的对象
  for (let i in obj) {
    result[i] = obj[i];
  }
  return result;
}

深拷贝

  • 判断值类型和引用类型
  • 判断数组还是对象
  • 递归
js
function deepClone(obj = {}) {
  // 1.判断是否为对象或null
  if (typeof obj !== 'object' || obj == null) {
    return obj;
  }

  // 2.初始化结果为对象或数组
  let result = obj instanceof Array ? [] : {};

  // 3.判断是否为自身的属性,递归拷贝
  for (let key in obj) {
    if (obj.hasOwnProperty(key)) {
      result[key] = deepClone(obj[key]);
    }
  }

  return result;
}

深度比较

  • 判断值类型
  • 判断内存地址
  • 判断 Key 个数
  • 遍历
js
function equal(obj1, obj2) {
  // 1.值类型
  if (!isObject(obj1) || !isObject(obj2)) {
    return obj1 === obj2;
  }

  // 2.内存地址是否相同
  if (obj1 === obj2) {
    return true;
  }

  // 3.Key 长度是否相等
  const obj1Keys = Object.keys(obj1);
  const obj2Keys = Object.keys(obj2);
  if (obj1Keys.length !== obj2Keys.length) {
    return false;
  }

  // 4.遍历 obj1 与 obj2 进行比较
  for (let key in obj1) {
    const res = equal(obj1[key], obj2[key]);
    if (!res) {
      return false;
    }
  }

  // 5.全部相等返回 true
  return true;
}

节流 & 防抖

节流

作用:无论点击多快,都会每隔 100ms 触发一次(单位时间只响应一次

例如:在拖拽滚动时,避免频繁触发事件

js
// 方法一:定时器
function throttle(fn, delay = 100) {
  let timer = null;
  return function () {
    if (timer) {
      return;
    }
    timer = setTimeout(() => {
      fn.apply(this, arguments);
      timer = null;
    }, delay);
  };
}

// 方法二:时间戳
function throttle2(fn, delay = 100) {
  let t1 = 0;
  return function () {
    let t2 = +new Date();
    if (t2 - t1 > delay) {
      fn.apply(this, arguments);
      t1 = t2;
    }
  };
}

防抖

作用:用户输入结束或暂停时,才会触发事件(只响应最后一次

例如:搜索框连续输入时,当输入结束,才显示搜索结果

js
function debounce(fn, delay = 500) {
  let timer = null;
  return function () {
    if (timer) {
      clearTimeout(timer);
    }
    timer = setTimeout(() => {
      fn.apply(this, arguments);
      timer = null;
    }, delay);
  };
}

数组去重

js
// 方法一:使用 Set
function unique(arr) {
  const set = new Set(arr);
  return [...set];
}

// 方法二:遍历数组
function unique2(arr) {
  return arr.filter((item, index, self) => {
    return self.indexOf(item) === index;
  });
}

数组扁平化

js
function flat(arr) {
  // 判断元素是否还有数组
  const isDeep = arr.some((item) => Array.isArray(item));
  if (!isDeep) {
    return arr;
  }

  // concat 可以扁平化一层数组
  const res = Array.prototype.concat.apply([], arr);
  return flat(res);
}

函数柯里化

手写 new

  1. 创建一个新的空对象
  2. 让这个新的对象的原型指向该构造函数的原型对象
  3. 执行构造函数,给这个新对象添加属性和方法
  4. 判断构造函数的返回值,来决定 new 的返回值(构造函数的返回值或者新对象)
js
function myNew(fn, ...args) {
  const obj = {};
  obj.__proto__ = fn.prototype;
  const res = fn.apply(obj, args);
  // 判断返回值是否为对象或函数
  if (typeof res === 'function' || (typeof res === 'object' && res != null)) {
    return res;
  }
  return obj;
}

手写 instanceof

原理:查找构造函数的原型对象是否在实例对象的原型链上,如果在返回 true,如果不在返回 false

js
function myInstanceof(target, origin) {
  // 1.判断传入 target 是否为引用类型
  if (typeof target !== 'function' && (typeof target !== 'object' || target == null)) {
    return false;
  }
  // 2.判断传入 origin 是否为函数类型(构造函数)
  if (typeof origin !== 'function') {
    throw new TypeError('origin must be function');
  }
  // 3.顺着原型链进行查找
  let proto = target.__proto__;
  while (proto) {
    if (proto === origin.prototype) {
      return true;
    } else {
      proto = proto.__proto__;
    }
  }
  return false;
}

手写 reduce

js
Array.prototype.myReduce = function (cb, init) {
  const arr = this;
  // 无默认值时,将起始值设置为数组第一项
  let total = init || arr[0];
  // 有默认值时,从数组第一项开始遍历
  for (let i = init ? 0 : 1; i < arr.length; i++) {
    total = cb(total, arr[i], i, arr);
  }
  return total;
};

手写 call & apply & bind

  1. 将方法挂载到传入的对象上(使用 Symbol 设置一个独一无二的方法)
  2. 执行对象上的方法
  3. 将这个方法从对象上删除
  4. 返回方法执行结果
js
Function.prototype.myCall = function (ctx, ...args) {
  const fn = Symbol();
  ctx[fn] = this;
  const result = ctx[fn](...args);
  delete ctx[fn];
  return result;
};

Function.prototype.myApply = function (ctx, args = []) {
  const fn = Symbol();
  ctx[fn] = this;
  const result = ctx[fn](...args);
  delete ctx[fn];
  return result;
};

Function.prototype.myBind = function (ctx, ...args1) {
  return (...args2) => {
    const fn = Symbol();
    ctx[fn] = this;
    const result = ctx[fn](...args1.concat(args2));
    delete ctx[fn];
    return result;
  };
};
关注微信公众号V.PS- 美国 CN2 GIA / 9929 / CMIN2 顶级线路
你认为这篇文章怎么样?
  • 0
  • 0
  • 0
  • 0
  • 0
  • 0

预览:

评论
  • 按正序
  • 按倒序
  • 按热度
Powered by Waline v3.1.3