前端高频考题(js)

一、延迟加载JS有哪些方式

  • defer:等html全部解析完成,才会执行js代码,顺次执行js脚本
  • async:async是和html解析同步的(一起解析),不是顺次执行js脚本(谁先加载完谁先执行)

二、js的数据类型有哪些

  • 基本类型:string、number、boolean、undefined、null、symbol、bigint
  • 引用类型:object

三、null和undefined的区别

  1. 作者在设计js的时候是先设计null的(因为设计之初借鉴了java语言)
  2. null会被隐式转换成0,很不容易发现错误
  3. 先有null后有undefined,出来undefined是为了填补之前的坑

四、==和===有什么不同

  • ==:比较的是值
    • string == number || boolean || number ... 都会隐式转换
    • 通过valueof转换(valueof()方法通常由JS在后台自动调用,并不显式地出现在代码中)
  • ===:除了比较值,还比较类型

五、js的微任务和宏任务

  • js是单线程的语言
  • 流程:同步 -> 事件循环【微任务和宏任务】 -> 微任务 -> 宏任务 -> 微任务 ...
    • 微任务:promise.then、process.nextTick ...
    • 宏任务:setTimeout、setInterval、setImmediate ...
css 复制代码
for(var i=0;i<3;i++){
    setTimeout(function(){
        console.log(i);
    },1000*i)
}
// 3 3 3
javascript 复制代码
setTimeout(function(){
    console.log('1') // 宏任务
})

new Promise((resolve)=>{
    console.log('2') // 同步
}).then(()=>{
    console.log('3') // 微任务
}).then(()=>{
    console.log('4') // 微任务
})

console.log('5') // 同步

// 2 5 3 4 1

六、js作用域

  1. 除了函数外,js是没有块级作用域的
  2. 作用域链:内部可以访问外部的变量,但是外部不能访问内部的变量(内部有优先找内部的)
  3. 注意声明变量是用var还是没有写(window.)
  4. js有变量提升的机制(变量悬挂声明)
  5. 优先级:声明变量 > 声明普通函数 > 参数 > 变量提升

怎么做

  • 先看本层作用域有没有此变量(注意变量提升)
  • js除了函数之外没有块级作用域
javascript 复制代码
(function(){
    var a = b = 10;
})()
console.log(a); // undefined
console.log(b); // 10
javascript 复制代码
function c(){
    var b = 1;
    function a(){
        conaole.log(b); // undefined
        var b = 2;
        console.log(b); // 2
    }
    a();
    console.log(b); // 1
}
javascript 复制代码
var name = 'a';
(function(){
    if(typeof name == 'undefined'){
        var name = 'b';
        console.log('111' + name); /// 111b
    }else{
        console.log('222' + name)
    }
})()
kotlin 复制代码
function fun(a){
    var a = 10;
    function a(){}
    console.log(a); // 10
}
fun(100)
}

七、js对象

  1. 对象是通过new操作符构建出来的,所以对象之间不相等
  2. 对象注意:引用类型
  3. 所有的key都是字符串类型
  4. 对象如何找属性|方法:对象本身 -> 构造函数中 -> 对象原型中 -> 构造函数原型中 -> 对象上一层原型中
arduino 复制代码
console.log([1,2,3] === [1,2,3]) // false
ini 复制代码
var obj1 = {
    a:'hello'
}

var obj2 = obj1;
obj2.a = 'world';
console.log(obj1); // {a:world''}
(function(){
    console.log(a); // undefined
    var a = 1;
})()
css 复制代码
var a = {};
var b = {
    key:'a';
}
var c = {
    key:'c';
}

a[b] = '123';
a[c] = '456';

console.log(a[b]); // 456
ini 复制代码
function Fun(){
    this.a = '在Fun函数中添加的';
}
Fun.prototype.a = '这是Fun原型添加的';
let obj = new Fun();
obj.a = '对象本身';
obj.__proto__.a = '这是对象原型添加的';

console.log(obj.a);

八、js作用域+this指向+原型链

ini 复制代码
var x = 10;
var obj = {
  x: 20,
  fn: function() {
    var x = 30;
    return function() {
      console.log(this.x);
    };
  }
};

obj.fn()(); // 10
var fn2 = obj.fn;
fn2()(); // 10

九、js判断变量是不是数组有哪些方法

  • isArray
ini 复制代码
var arr = [1,2,3];
console.log(Array.isArray(arr));
  • instanceof
ini 复制代码
var arr = [1,2,3];
console.log(arr instanceof Array);
  • 原型prototype
javascript 复制代码
var arr = [1,2,3];
console.log(Object.prototype.toString.call(arr).indexOf('Array') > -1);
  • isPrototypeOf()
ini 复制代码
var arr = [1,2,3];
console.log(Array.prototype.isPrototypeOf(arr));
  • constructor
less 复制代码
var arr = [1,2,3];
console.log(arr.constructor.toString().indexOf('Array') > -1);

十、slice和splice是用来干嘛的

  • slice 是用来截取的,不改变原数组
arduino 复制代码
const arr = [1, 2, 3, 4, 5];

// 从索引2开始截取到末尾
console.log(arr.slice(2)); // [3, 4, 5]

// 从索引1截取到索引3(不包含3)
console.log(arr.slice(1, 3)); // [2, 3]

// 使用负数索引
console.log(arr.slice(-2)); // [4, 5](从倒数第2个元素到末尾)
console.log(arr.slice(1, -1)); // [2, 3, 4](从索引1到倒数第1个元素前)
  • splice 是用来插入、删除、替换的,会修改原数组
arduino 复制代码
const arr = [1, 2, 3, 4, 5];

// 删除元素
const deleted = arr.splice(2, 2); // 从索引2开始删除2个元素
console.log(arr); // [1, 2, 5]
console.log(deleted); // [3, 4]

// 插入元素(不删除)
arr.splice(1, 0, 'a', 'b'); // 从索引1开始插入元素
console.log(arr); // [1, 'a', 'b', 2, 5]

// 替换元素
arr.splice(2, 1, 'x'); // 从索引2开始删除1个元素,插入'x'
console.log(arr); // [1, 'a', 'x', 2, 5]

十一、js数组去重

  • set
sql 复制代码
var arr1 = [1,2,3,1];
function unique(arr){
    return [...new Set(arr)]
    // return Array.from(new Set(arr1))
}
  • for循环
ini 复制代码
var arr2 = [1,2,3,1];
function unique(arr){
    var brr = [];
    for(var i=0;i<arr.length;i++){
        if(brr.indexOf(arr[i]) == -1){
        brr.push(arr[i]);
        }
    }
    return brr;
}

十二、多维数组的最大值

lua 复制代码
var arr = [[1,2,3,4],[5,6,7,8],[9,10,11,12]];

function fe(arr){
    var brr = [];
    arr.forEach((item,index)=>{
        brr.push(Math.max(...item))
    })
    return brr;
}

十三、给字符串新增方法

javascript 复制代码
String.prototype.addPrefix = function(str){
    return str + this;
}

console.log('world'.addPrefix('hello')) // helloworld

十四、字符串出现最多次数的字符及次数

ini 复制代码
var str = 'aaabbbbcccdddddddd'

var obj = {};
for(var i=0;i<str.length;i++){
    var char = str.charAt(i);
    if(obj[char]){
        obj[char]++;
    }else{
        obj[char]=1;
    }
}
// 统计最大值
var max = 0;
for(var key in obj){
    if(max < obj[key]){
        max = obj[key];
    }
}
// 比对
for(var key in obj){
    if(obj[key] == max){
        console.log('最多的字符串为' + key);
        console.log('出现的次数为' + max);
    }
}

十五、new操作符具体做了什么

javascript 复制代码
function Fun(age,name){
    this.age = age;
    this.name = name;
}

function create(fn,...args){
    // 1.创建一个空对象
    let obj = {};
    // 2.将空对象的原型,指向于构造函数的原型
    Object.setPrototypeOf(obj,fn.prototype);
    // 3.将空对象作为构造函数的上下文(改变this指向)
    let result = fn.apply(obj,args);
    // 4.对构造函数有返回值的处理判断
    return result instanceof Object ? result : obj;
}

console.log(create(Fun,18,'张三'))

十六、闭包

  1. 闭包是什么:闭包是一个函数加上到创建函数的作用域的连接,闭包关闭了函数的自由变量
  2. 闭包解决什么问题:
  • 内部函数可以访问到外部函数
ini 复制代码
var lis = document.getElementsByTagName('li');
for(var i=0;i<lis.length;i++){
    (function(i){
        lis[i].onclick = function(){
            alert(i);
        }
    })(i)
} // 也可以直接使用let i=0
  1. 闭包的缺点
  • 变量会驻留在内存中,造成内存损耗问题(把闭包的函数设置为null)

十七、原型链

  1. 原型可以解决什么问题
  • 对象共享属性和共享方法
  1. 谁有原型
  • 函数有 prototype
  • 对象有 __proto__
  1. 对象查找属性或者方法的顺序
  • 现在对象本身属性查找 -> 再到构造函数中查找 -> 再到__proto__中查找 -> 再到prototype中查找 -> 再到原型的原型 -> 最后到null
  1. 原型链的使用
  • 扩展内置对象的功能
  • 复用方法与属性

十八、深拷贝和浅拷贝

共同点:复制

  • 浅拷贝:只复制引用,而未复制真正的值
ini 复制代码
var arr1 = ['a','b','c'];
var arr2 = arr1;
  • 深拷贝:是复制真正的值,不同引用
ini 复制代码
var obj1 = {
    a:1;
    b:2;
}

var obj2 = JSON.parse(JSON.stringify(obj3));

十九、var、let、const

  • 共同点

    • 都可以声明变量
  • 区别1

    • var 具有变量提升的机制
    • let、const 没有变量提升的机制
  • 区别2

    • var 可以多次声明同一个变量
    • let、const 不可以多次声明同一个变量
  • 区别3

    • var、let 声明变量
    • const 声明常量
    • var、let 声明的变量可以再次赋值,但是cosnt不可以再次赋值了
  • 区别4

    • var 声明的变量没有自身的作用域
    • let、const 声明的变量有自身的作用域

二十、箭头函数和普通函数有什么区别

  • this 指向的问题
    • 箭头函数中的this是在箭头函数定义时就决定的,而且不可修改(call、apply、bind)
    • 箭头函数的this指向外层第一个普通函数的this
  • 箭头函数不能new(不能当作构造函数)
  • 箭头函数 prototype 为 undefined,没有原型

二十一、sort()背后的原理是什么

  • V8引擎sort函数只给出了两种排序 InsertionSort(数量小于10的数组) 和 QuickSort(数量大于10的数组)
  • 之前的版本是:插入排序和快排,现在是冒泡
css 复制代码
var arr1 = arr2.sort(function(a,b){
    return a-b
}) // 从小到大

二十二、js数组常见方法

  • 添加 / 删除元素:

    • push (...items) --- 从结尾添加元素,
    • pop () --- 从结尾提取元素,
    • shift () --- 从开头提取元素,
    • unshift (...items) --- 从开头添加元素,
    • splice (pos, deleteCount, ...items) --- 从 index 开始:删除 deleteCount 元素并在当前位置插入元素。
    • slice (start, end) --- 它从所有元素的开始索引 "start" 复制到 "end"(不包括 "end")返回一个新的数组。
    • concat (...items) --- 返回一个新数组:复制当前数组的所有成员并向其中添加 items 。如果有任何 items 是一个数组,那么就取其元素。
  • 查询元素:

    • indexOf/lastIndexOf (item, pos) --- 从 pos 找到 item,则返回索引否则返回 -1。
    • includes (value) --- 如果数组有 value,则返回 true,否则返回 false。
    • find/filter (func) --- 通过函数过滤元素,返回 true 条件的符合 find 函数的第一个值或符合 filter 函数的全部值。
    • findIndex 和 find 类似,但返回索引而不是值。
  • 转换数组:

    • map (func) --- 从每个元素调用 func 的结果创建一个新数组。
    • sort (func) --- 将数组排序排列,然后返回。
    • reverse () --- 在原地颠倒数组,然后返回它。
    • split/join --- 将字符串转换为数组并返回。
    • reduce (func, initial) --- 通过为每个元素调用 func 计算数组上的单个值并在调用之间传递中间结果。

二十三、虚拟dom

  • 浏览器操作dom是很消耗性能的,需经历 解析 HTML,CSS → 构建 DOM,CSSDOM 树 → 生成渲染树render → 布局(Layout)→ 绘制(Paint)→ 合成(Composite) 等流程,直接操作真实 DOM 会触发多次重排 / 重绘,性能显著下降。
  • 虚拟dom:描述元素和元素之间的关系,创建一个js对象模拟真实的dom,如果组件内有响应式数据,数据发生改变的时候,render函数会生成一个新的虚拟dom,这个新的虚拟dom会和旧的虚拟dom进行比对,找到需要修改虚拟dom的内容,然后去对应真实dom中修改
  • diff算法:虚拟dom比对时使用,返回一个patch对象,这个对象的作用就是储存两个节点不同的地方,最后用patch里记录的信息更新真实的dom

Diff 算法采用深度优先遍历同层比较策略,遵循以下原则:

  1. 树分层比较:只比较同层级节点。
  2. 不同类型节点:直接替换整个节点及其子节点。
  3. 相同类型节点:仅更新节点属性和内容,复用子节点。
  4. 列表节点优化 :通过key属性识别相同节点,减少不必要的移动和创建。

二十四、call、apply、bind的区别

  • 都是改变this指向和函数的调用,call和apply的功能类似,只是传参的方法不同
  • call 方法传的是一个参数列表
  • apply 传递的是一个数组
  • bind 传参后不会立刻执行,会返回一个改变了this指向的函数,这个函数还是可以传参的,bind()()

二十五、ES6新特性

  1. 新增块级作用域(let,const)
  2. 新增定义类的语法糖(class)
  3. 新增一种基本数据类型(symbol)
  4. Promise
  5. 新增模块化(import,export)
  6. 箭头函数

二十六、TDZ

特性 let const var
块级作用域
变量提升 ✅(但存在 TDZ) ✅(但存在 TDZ)
TDZ
必须初始化
允许重复声明
可重新赋值

二十七、some 和 every

  • some() :只要数组中至少有一个元素 满足条件,就返回true,否则返回false
  • every() :只有数组中所有元素 都满足条件,才返回true,否则返回false

二十八、cookie、localStorage、sessionStorage 的区别

特性 cookie localStorage sessionStorage
存储容量 一般约 4KB,同一域名下数量有限制 一般为 5MB - 10MB 一般为 5MB - 10MB
数据有效期 可设置过期时间,不设置则为会话级别,浏览器关闭即消失 持久化存储,手动删除才会消失 仅在当前会话有效,浏览器窗口关闭数据删除
作用域 同一域名下所有页面可访问,通过 path 属性可限制访问路径 同一域名下所有页面共享,不同域名无法访问 同一域名下,不同浏览器窗口或标签页(非 window.open 打开)无法共享,window.open 打开的子窗口与父窗口可共享
数据传输 每次 HTTP 请求会自动发送到服务器,增加额外开销 仅存储在本地,需通过 JavaScript 代码手动发送到服务器 仅存储在本地,需通过 JavaScript 代码手动发送到服务器
用途 身份验证、记录登录状态、跟踪用户行为等 存储用户配置信息、本地缓存数据等 临时存储当前会话中需共享的数据,如多步骤表单填写过程中的数据
安全性 易受 XSS 和 CSRF 攻击,可通过设置 HttpOnly 提高安全性 可能受恶意软件或社会工程学手段攻击,可加密存储数据提高安全性 安全性与 localStorage 类似,因生命周期短,数据泄露风险相对较小

Cookie 本身是受同源策略限制的,默认情况下不允许跨域访问,但可以通过特定配置实现跨域场景下的 Cookie 传递与访问。以下是详细说明:

完全跨域(如 a.comb.com

需通过 CORS(跨域资源共享) 配合后端配置实现:

  • 前端请求 :需在 AJAX 请求中添加 withCredentials: true(表示允许携带跨域 Cookie)。

    php 复制代码
    fetch('https://b.com/api', {
      method: 'GET',
      credentials: 'include' // 或 XMLHttpRequest 的 withCredentials: true
    });
  • 后端响应:需返回以下 HTTP 头,允许跨域携带 Cookie:

    http

    arduino 复制代码
    Access-Control-Allow-Origin: https://a.com  // 必须指定具体域名,不能用 *
    Access-Control-Allow-Credentials: true     // 允许携带 Cookie
    • 注意:Access-Control-Allow-Origin 不能设为 *,必须明确指定前端域名,否则浏览器会拒绝接收 Cookie。

二十九、在哪里存 taken

存储位置 优势 劣势
Cookie 方便在客户端和服务器间传递,设置 HttpOnly 可防 XSS 攻击 增加 HTTP 请求流量开销,易受 CSRF 攻击
LocalStorage 存储容量大,数据持久化,便于后续会话使用 可被客户端脚本读取修改,有安全风险,需手动管理过期时间
SessionStorage 数据仅在当前会话有效,窗口关闭自动删除,安全风险低 不同窗口或标签页间无法共享,不适用多窗口共享登录状态场景
内存 在应用各组件中访问快速,不受同源策略限制,页面关闭数据清除 应用异常崩溃或关闭时 Token 丢失,复杂应用中状态管理较复杂

三十、js垃圾回收机制

标记清除(Mark and Sweep)

这是最基础的算法,现代引擎在此基础上优化:

  1. 标记阶段:从根对象开始,递归标记所有可达对象。
  2. 清除阶段:遍历堆内存,回收未被标记的对象。
  3. 内存整理(可选):移动对象以合并碎片空间。

标记整理(Mark and Compact)

在标记清除的基础上,回收后将存活对象移动到连续内存空间,减少碎片。

分代回收(Generational Garbage Collection)

基于 "多数对象生命周期短暂" 的观察,将对象分为:

  • 新生代(Young Generation) :新创建的对象(如局部变量),频繁垃圾回收。
  • 老生代(Old Generation) :长期存活的对象(如全局变量),较少垃圾回收。

三十一、一个DOM事件从产生到触发的流程是怎么样的

  • 事件捕获:事件产生后,浏览器会从文档根节点(document 开始,向下逐层传播到目标元素的父级。
  • 事件委托:当子元素触发事件后,事件冒泡到父元素,父元素的监听器通过 event.target 判断事件来源并处理。
  • 事件冒泡:返回根节点

三十二、object和map的区别

特性 Object Map
键的类型 字符串或 Symbol 任意类型(包括对象、函数等)
顺序性 不保证顺序(ES2015+ 字符串键按插入顺序,但非强制) 键值对按插入顺序排列,迭代时保持顺序
默认键 原型链上的属性可能造成冲突(如 toString 无默认键,仅包含显式添加的键值对
键值对数量 需手动计算(如 Object.keys(obj).length 直接通过 size 属性获取
性能 大规模数据操作时性能较差 频繁增删键值对场景下性能更优
迭代支持 需手动转换为数组(如 Object.entries() 直接支持 for...offorEach 等迭代方法
序列化 可通过 JSON.stringify() 序列化 默认不支持序列化,需手动处理
相关推荐
Lhy@@21 小时前
Axios 整理常用形式及涉及的参数
javascript
练习时长一年21 小时前
Spring代理的特点
java·前端·spring
水星记_21 小时前
时间轴组件开发:实现灵活的时间范围选择
前端·vue
2501_930124701 天前
Linux之Shell编程(三)流程控制
linux·前端·chrome
潘小安1 天前
『译』React useEffect:早知道这些调试技巧就好了
前端·react.js·面试
@大迁世界1 天前
告别 React 中丑陋的导入路径,借助 Vite 的魔法
前端·javascript·react.js·前端框架·ecmascript
EndingCoder1 天前
Electron Fiddle:快速实验与原型开发
前端·javascript·electron·前端框架
EndingCoder1 天前
Electron 进程模型:主进程与渲染进程详解
前端·javascript·electron·前端框架
Nicholas681 天前
flutter滚动视图之ScrollNotificationObserve源码解析(十)
前端
@菜菜_达1 天前
CSS scale函数详解
前端·css