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 或 functioninstanceof
用于检测引用类型
DOM 元素高度
不能使用箭头函数
精度丢失
原因
- 在 JavaScript 中,采用 IEEE754 规范中64位双精度浮点数编码
- 对于64位的双精度浮点数,最高的1位是符号位S,11位是指数E,52位为有效数字M。
- 0.1 + 0.2 转换为二进制后,为无限不循环小数,只保留52位有效数字,就会造成精度丢失
解决方案
- 第三方库:
math.js
decimal.js
- 原生方法:使用
toFixed()
四舍五入 - 转换为整数后进行运算
输入 URL 的过程
加载过程
- DNS 解析:域名 -> IP 地址
- 浏览器根据 IP 地址向服务器建立 TCP 连接
- 发起 HTTP 请求
- 服务器处理 HTTP 请求,并返回给浏览器
渲染过程
- 根据 HTML 代码生成 DOM Tree
- 根据 CSS 代码生成 CSSOM
- 将 DOM Tree 和 CSSOM 整合形成 Render Tree
- 根据 Render Tree 渲染页面
- 遇到
<script>
则暂停渲染,优先加载并执行 JS 代码 - 直至把 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
- 创建一个新的空对象
- 让这个新的对象的原型指向该构造函数的原型对象
- 执行构造函数,给这个新对象添加属性和方法
- 判断构造函数的返回值,来决定 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
- 将方法挂载到传入的对象上(使用 Symbol 设置一个独一无二的方法)
- 执行对象上的方法
- 将这个方法从对象上删除
- 返回方法执行结果
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;
};
};
预览: