复盘行动-高频面试题【js篇】

前言

借鉴了网上一些资料,整理了一些 JavasSript 相关的高频面试题,方便自己随时查阅复习、查缺补漏,温故而知新!也希望帮助到其他同学。如有错误,欢迎评论区指正!

js重点

作用域&&作用域链

  • 作用域: 规定了如何查找变量,也就是确定当前执行代码对变量的访问权限。换句话说,作用域决定了代码区块中变量和其他资源的可见性(全局作用域、函数作用域、块级作用域)

  • 作用域链: 从当前作用域开始一层层往上找某个变量,如果找到全局作用域还没找到,就放弃寻找。这种层级关系就是作用域链。(由多个执行上下文的变量对象构成的链表就叫做作用域链)

js原型&&原型链

原型:

当试图访问一个对象的属性时,它不仅在该对象上搜寻,还会搜寻该对象的原型,以及该对象的原型的原型,依次层层向上搜索,直到找到一个名字匹配的属性或到达原型链的末尾。

准确地说,这些属性和方法 定义在Object的构造器函数(constructor functions)之上的prototype属性上,而非实例对象本身。

例子:

js 复制代码
 function doSomething(){}
 console.log(doSomething.prototype);

输出原型对象,原型对象有一个自有属性constructor,这个属性指向该函数:

js 复制代码
 {
     constructor: ƒ doSomething(),
     __proto__: {
         constructor: ƒ Object(),
         hasOwnProperty: ƒ hasOwnProperty(),
         isPrototypeOf: ƒ isPrototypeOf(),
         propertyIsEnumerable: ƒ propertyIsEnumerable(),
         toLocaleString: ƒ toLocaleString(),
         toString: ƒ toString(),
         valueOf: ƒ valueOf()
     }
 }

constructor又指向 doSomething 该函数

原型链:

  • 原型可以解决什么问题?

    对象共享属性、共享方法

  • 谁有原型?

    函数拥有:prototype、对象拥有:__proto__

  • 对象查找属性或者方法的顺序?

    先在对象本身查找 --> 构造函数中查找 --> 对象的原型 --> 构造函数的原型中 --> 当前原型的原型中查找; 这个查找的过程称之为:原型链,原型链的最顶端是null。

  • __proto__ 原型指针
    __proto__作为不同对象之间的桥梁,用来指向创建它的构造函数的原型对象的

    每个对象的__proto__都是指向它的构造函数的原型对象prototype

总结:

  • 一切对象都是继承自Object对象,Object 对象直接继承根源对象null
  • 一切的函数对象(包括 Object 对象),都是继承自 Function 对象
  • Object 对象直接继承自 Function 对象
  • Function对象的__proto__会指向自己的原型对象,最终还是继承自Object对象

new操作符做了什么

new操作符共经历了四个过程:

  • 1、首先创一个新的空对象
  • 2、设置空对象的 __proto__ 为构造函数的 prototype
  • 3、让fn的this指向新对象obj,并执行fn的函数体 (构造函数fn的this 指向这个对象,执行构造函数的代码(为这个新对象添加属性))
  • 4、判断fn的返回值类型,如果是引用类型,就返回这个引用类型的对象。如果是值类型,返回obj

手写一个new操作符:

js 复制代码
function myNew (fn,...args) {
  // 1、创建一个空对象
  // const obj = new Object()
  // 2、新对象obj 的_proto_指针指向构造函数fn的prototype属性
  // obj.__proto__ = fn.prototype
  // 1、2步合并写法
  const obj = Object.create(fn.prototype)
  // 3、将构造函数fn 内部的this指向新对象(obj), this指向新对象,并执行构造函数代码
  const res = fn.apply(obj,args) // apply 接受的参数是数组
  // 4、判断结果,如果构造函数返回的是对象,则使用构造函数执行的结果res。否则,返回新创建的新对象obj
  return typeof res === Object ? res : obj
}

使用:

js 复制代码
// 创建一个构造函数
function People(name,age) {
    this.name = name; // 实例上的属性
    this.age = age; // 实例上的属性
}

const ada =  myNew(People, '张三',18)
console.log('-----:',ada.name) // 张三
console.log('-----:',ada.age) // 18

js中this指向

this是什么?

this是一个对象,但是我们不同的操作 this指向的对象是不相同的

5种this指向情况:

  • 当函数作为对象的方法被调用时,this就会指向该对象
  • 作为普通函数执行时,this指向全局变量window
  • 构造器调用(使用new),this指向返回的这个对象
  • 箭头函数的this绑定的是this所在函数定义在哪个对象下,就绑定哪个对象。如果有嵌套的情况,则this绑定到最近的一层对象上。
  • 基于Function.prototype上的 apply 、 call 和 bind 调用模式,这三个方法都可以显式的指定调用函数的 this 指向:
    apply接收参数的是数组,
    call接受参数是列表,
    bind方法通过传入一个对象,返回一个this 绑定了传入对象的新函数。这个函数的 this指向除了使用new 时会被改变,其他情况下都不会改变。若为空默认是指向全局对象window

js闭包

概念: 一个定义在函数内部的函数,可以读取到其他函数内部变量的函数,本质上,闭包就是一个把函数内部和外部连接起来的桥梁。

一句话总结: 闭包就是能够读取其他函数内部变量的函数

闭包原理: 利用了作用域链的特性,作用域链就是在当前执行环境下访问某个变量时,如果不存在就一直向外层寻找,最终寻找到最外层也就是全局作用域,这样就形成了一个链条。
闭包用途:

封装私有化变量

模仿块级作用域

保护外部函数的变量 能够访问函数定义时所在的词法作用域(阻止其被回收)

创建模块
缺点: 变量会驻留在内存中,造成内存损耗,不恰当的使用闭包可能会造成内存泄漏的问题

闭包应用案例分析

需求:实现变量a 自增

  • 1、通过全局变量可以实现,但会污染其他程序
js 复制代码
var a = 10;
function Add(){
    a++;
    console.log(a);
}
Add();
Add();
Add();
  • 2、定义一个局部变量,不污染全局,但是实现不了递增
js 复制代码
var a = 10;
function Add2(){
    var a = 10;
    a++;
    console.log(a);
}
Add2();
Add2();
Add2();
console.log(a);
  • 3、通过闭包实现,可以使函数内部局部变量递增,不会影响全部变量,完美!!
js 复制代码
var a  = 10;
function Add3(){
    var a = 10;
    return function(){
        a++;
        return a;
    };
};
var cc =  Add3();
console.log(cc());
console.log(cc());
console.log(cc());
console.log(a);

闭包其他应用:

  • 从外部读取函数内部的变量

    js 复制代码
    function f1(){
        var n=111;
        function f2(){
          console.log(n); 
        }
        return f2;
    }
    var result = f1();
    result(); // 111
    // 把f2作为返回值,就可以在f1外部读取它的内部变量
  • 将创建的变量的值始终保持在内存中

    js 复制代码
    function f1() {
      var n = 12;
      function f2() {
         console.log(++n);
      };
      return f2;
    }
    var result = f1();
    result();//13
    //函数f1中的局部变量n一直保存在内存中,并没有在f1调用后被销毁
  • 封装对象的私有属性和私有方法

    js 复制代码
    function f1(n) {
      return function () {
        return n++;
      };
    }
    var a1 = f1(1);
    a1() // 1
    a1() // 2
    a1() // 3
    var a2 = f1(5);
    a2() // 5
    a2() // 6
    a2() // 7
    //这段代码中,a1 和 a2 是相互独立的,各自返回自己的私有变量。

防抖、节流

防抖: n 秒后在执行该事件,若在 n 秒内被重复触发,则重新计时

防抖场景:

  • 搜索框搜索输入;只需用户最后一次输入完,再发送请求
  • 手机号、邮箱验证输入检测
  • 窗口大小resize;只需窗口调整完成后,计算窗口大小,防止重复渲染

节流: n 秒内只运行一次,若在 n 秒内重复触发,只有一次生效

节流场景:

  • 滚动加载更多
  • 搜索框搜索联想功能
  • 高频点击
  • 表单重复提交

浅拷贝、深拷贝

  • 浅拷贝:

    只复制了引用,而不复制真正的值,新旧对象还是共享同一块内存(分支)

    方法:

    • 扩展运算符: ...
    • Object.assign()
    • slice
    • concat

    缺点: 浅拷贝只能拷贝第一层的数据,且都是值类型数据,当对象内嵌套有对象的时候,被嵌套的对象进行的还是浅拷贝

  • 深拷贝:

    会另外创造一个一模一样的对象,新对象跟原对象不共享内存,修改新对象不会影响原对象,是"值"而不是"引用"(不是分支)

    方法:

    • JSON.parse(JSON.stringify())

      当要拷贝的数据中含有undefined/function/symbol类型会忽略;

      不可以对Function进行拷贝,因为JSON格式字符串不支持Function,在序列化的时候会自动删除; Map, Set, RegExp, Date, ArrayBuffer 和其他内置类型在进行序列化时会丢失;

    • jQueryextend

    • lodash_.cloneDeep()

    • 手写一个递归实现深拷贝

      例:(有循环引用的问题)

      js 复制代码
          function cloneDeep(obj){
            const newObj = {};
            let keys = Object.keys(obj);
            let key = null;
            let data = null;
            for(let i = 0; i<keys.length;i++){
              key = keys[i];
              data = obj[key];
              if(data && typeof data === 'object'){
                newObj[key] = cloneDeep(data)
              }else{
                newObj[key] = data;
              }
            }
            return newObj
          }

js的垃圾回收机制

Javascript 具有自动垃圾回收机制,会定期对那些不再使用的变量、对象所占用的内存进行释放,原理就是找到不再使用的变量,然后释放掉其占用的内存。

垃圾回收策略

  • 标记清除( Mark-Sweep )

目前在 JavaScript引擎 里这种算法是最常用的,到目前为止的大多数浏览器的 JavaScript引擎 都在采用标记清除算法,只是各大浏览器厂商还对此算法进行了优化加工,且不同浏览器的 JavaScript引擎 在运行垃圾回收的频率上有所差异。

此算法分为 标记 和 清除 两个阶段,标记阶段:即为所有活动对象做上标记。清除阶段:则把没有标记(也就是非活动对象)销毁

首先它会遍历堆内存上所有的对象,分别给它们打上标记,然后在代码执行过程结束之后,对所使用过的变量取消标记。在清除阶段再把具有标记的内存对象进行整体清除,从而释放内存空间。

优点:简单

缺点:内存碎片化、分配速度慢

  • 引用计数( Reference Counting )

这其实是早先的一种垃圾回收算法,它把对象是否不再需要简化定义为对象有没有其他对象引用到它,如果没有引用指向该对象(零引用),对象将被垃圾回收机制回收,但因为它的问题很多,目前很少使用这种算法了。

详情参考: Javascript的垃圾回收机制知多少? - 掘金 (juejin.cn)

js内存泄露

内存泄漏,指在JS中已经分配内存地址的对象由于长时间未进行内存释放或无法清除 ,造成了长期占用内存,使得内存资源浪费, 最终导致运行的应用响应速度变慢以及最终崩溃的情况。

造成内存泄露的原因:

  • 滥用闭包
  • 意外的全局变量
  • 被遗忘的定时器、回调函数
  • 脱离DOM的引用
  • 注意程序逻辑,避免编写『死循环』之类的代码
  • DOM对象和JS对象相互引用
  • 反复重写同一个数据会造成内存大量占用,但是IE浏览器关闭后会被释放。

js变量提升

变量提升(hoisting)是用于解释代码中变量声明行为的术语。
使用 var 关键字声明或初始化的变量,会将声明语句"提升"到当前作用域的顶部 (也就是说在变量声明前访问它是可以访问到该变量的,只不过它的值是 undefined

但是,只有声明才会触发提升,而赋值语句(如果有的话)将保持原样。

  • var、let声明变量
js 复制代码
// 用 var 声明会得到提升,也就是在声明前访问也是可以的
console.log(foo); // undefined
var foo = 1;
console.log(foo); // 1

// 用 let/const 声明不会提升,声明前访问直接报错
console.log(bar); // ReferenceError: bar is not defined
let bar = 2;
console.log(bar); // 2
  • 函数声明

函数声明会使函数体提升 ,也就是声明之前可以访问到;

函数表达式 (以声明变量的形式书写)只有变量声明会被提升。

js 复制代码
// 函数声明,声明前访问可以访问到函数体
console.log(foo); // [Function: foo]
foo(); // 'FOOOOO'
function foo() {
  console.log('FOOOOO');
}
console.log(foo); // [Function: foo]

// 函数表达式声明时,bar该变量声明会被提升,此时的函数体不会提升也就报错了
console.log(bar); // undefined
bar(); // Uncaught TypeError: bar is not a function
var bar = function () {
  console.log('BARRRR');
};
console.log(bar); // [Function: bar]

js执行上下文

执行上下文(Execution Context)是JavaScript中的一个核心概念。它代表了代码被执行时的环境和条件。换句话说,执行上下文是一个抽象的概念,涵盖了变量、函数和它们的作用域。
每当我们运行JavaScript代码时,都会创建一个执行上下文。这个上下文可以是全局的,也可以是与特定函数相关的。理解执行上下文对于我们正确理解代码的运行机制至关重要。

执行上下文的种类

在JavaScript中,存在不同类型的执行上下文,每种都在特定的情况下创建。

全局执行上下文

全局执行上下文是代码中最外层的上下文,它在整个脚本执行期间都存在。在浏览器环境中,全局执行上下文通常与window对象相关联。

函数执行上下文

每次调用函数时,都会创建一个新的函数执行上下文。函数执行上下文与全局执行上下文类似,但它仅在函数调用期间存在。

块级执行上下文

ES6引入了letconst关键字,它们允许我们创建块级作用域。块级执行上下文与函数执行上下文类似,但它们存在于用花括号 {} 定义的块中。

描述事件冒泡

当一个事件在 DOM 元素上触发时,如果有事件监听器,它将尝试处理该事件,然后事件冒泡到其父级元素,并发生同样的事情。

最后直到事件到达祖先元素。事件冒泡是实现事件委托的原理。

微任务、宏任务(Event Loop)

概念: js 是一种单线程语言

简单来说:只有一条通道,那么在任务多的情况下,就会出现拥挤的情况,这种情况下就产生了 多线程 ,但是这种多线程 是通过单线程模仿的,也就是假的。那么就产生了同步任务异步任务 微任务:DOM 渲染前 触发;宏任务:DOM 渲染后触发;

宏任务(macrotask) 微任务(microtask)
谁发起的 宿主(Node、浏览器) JS引擎
具体事件 1.script(可以理解为外层同步代码); 2.setTimeout/setInterval; 3.UI rendering/UI事件; 4.DOM 事件; 5.setImmediate(Node.js) 1.Promise; 2.MutaionObserver; 3.Object.observe(已废弃;Proxy 对象替代);4.process.nextTick(Node.js)
谁先运行 后运行 先运行
会触发新一轮Tick吗 不会

Event Loop运行机制

在事件循环中,每进行一次循环操作称为 tick,每一次 tick 的任务处理模型是比较复杂的,但关键步骤如下:

  • 执行一个宏任务(栈中没有就从事件队列中获取)
  • 执行过程中如果遇到微任务,就将它添加到微任务的任务队列中
  • 宏任务执行完毕后,立即执行当前微任务队列中的所有微任务(依次执行)
  • 当前宏任务执行完毕,开始检查渲染,然后GUI线程接管渲染
  • 渲染完毕后,JS线程继续接管,开始下一个宏任务(从事件队列中获取)

例一

js 复制代码
setTimeout(function(){
	console.log('1');
});
new Promise(function(resolve){		    
	console.log('2');
	resolve();
}).then(function(){		    
	console.log('3');
}).then(function(){
console.log('4')
}); 		
console.log('5');
//输出为: 2 5 3 4 1

分析:

1.遇到setTimout,异步宏任务,放入宏任务队列中

2.遇到new Promise,new Promise在实例化的过程中所执行的代码都是同步进行的,所以输出2

3.而Promise.then中注册的回调才是异步执行的,将其放入微任务队列中

4.遇到同步任务console.log('5');输出5;主线程中同步任务执行完

5.从微任务队列中取出任务到主线程中,输出3、 4,微任务队列为空

6.从宏任务队列中取出任务到主线程中,输出1,宏任务队列为空

浏览器输入url回车后发生了什么

  1. 读取缓存:搜索自身的 DNS 缓存(如果 DNS 缓存中找到IP 地址就跳过了接下来查找 IP 地址步骤,直接访问该 IP 地址)
  2. DNS 解析: 将域名解析成 IP 地址
  3. TCP 连接:TCP 三次握手,简易描述三次握手:
    • 客户端:服务端你在么?
    • 服务端:客户端我在,你要连接我么?
    • 客户端:是的,我要链接。连接打通,可以开始请求了
  4. 发送 HTTP 请求
  5. 服务器处理请求并返回 HTTP 报文
  6. 浏览器解析渲染页面
    • 根据 HTML 解析出 DOM 树
    • 根据 CSS 解析生成 CSS 规则树
    • 结合 DOM 树和 CSS 规则树,生成渲染树
    • 根据渲染树计算每一个节点的信息
    • 根据计算好的信息绘制页面
  7. 断开连接:TCP 四次挥手

什么是高阶函数

  • 可以把函数当作参数传递给另外一个函数

  • 可以把函数当作另一个函数的返回结果

使用高阶函数的意义:

抽象可以帮我们屏蔽细节,只需要关注于我们的目标,高阶函数用来抽象通用的问题

常用高阶函数: forEach、map、filter、sort、some、reduce等

高频八股文

js 数据类型

基本类型

js有 8 种基础的数据类型,分别为: UndefinedNullBooleanNumberStringObjectSymbolBigInt

SymbolBigInt 是 ES6 新增的数据类型

  • Symbol 代表独一无二的值,最大的用法是用来定义对象的唯一属性名。
  • BigInt 可以表示任意大小的整数。

引用类型 Object

js内置引用类型:ArrayFunctionDateRegExp

基本类型和引用类型主要区别

存放位置:

  • 基本数据类型:基本类型值在内存中占据固定大小,直接存储在栈内存中的数据
  • 引用数据类型:引用类型在栈中存储了指针,这个指针指向堆内存中的地址,真实的数据存放在堆内存

比较:

  • 基本数据类型: 基本类型的比较是值的比较,只要它们的值相等就认为他们是相等的
  • 引用数据类型: 引用数据类型的比较是引用的比较,看其的引用是否指向同一个对象

typeof 与 instanceof的区别?

  • typeof

typeof是一个操作符,其右侧跟一个一元表达式,并返回这个表达式的数据类型。返回的结果用该类型的字符串(全小写字母)形式表示,包括以下 7 种:number、boolean、symbol、string、object、undefined、function 等。

能判断所有值类型,函数。不可对 null、对象、数组进行精确判断,因为都返回 object

  • instanceof

instanceof是用来判断 A 是否为 B 的实例,表达式为:A instanceof B

如果A是B的实例,则返回 true ,否则返回 false 。 在这里需要特别注意的是:instanceof 检测的是原型。
instanceof 判断两个对象是否属于实例关系,而不能判断一个对象实例具体属于哪种类型。

能判断对象类型,不能判断基本数据类型,其内部运行机制是判断在其原型链中能否找到该类型的原型。

instanceof原理

instanceof 用于检测一个对象是否为某个构造函数的实例。

例如:A instanceof B
instanceof 用于检测对象 A 是不是 B 的实例,而检测是基于原型链进行查找的,也就是说 Bprototype 有没有在对象 A 的__proto __ 原型链上,如果有就返回 true ,否则返回 false

为什么ES6会新增Promise

ES6 以前,解决异步的方法是回调函数。但是回调函数有一个最大的问题就是回调地狱(callback hell),当我们的回调函数嵌套的层数过多时,就会导致代码横向发展。

Promise 的出现就是为了解决回调地狱的问题。

同步和异步的JavaScript代码执行方式

同步代码是按照顺序执行的代码,每个任务必须等待前一个任务完成后才能执行。同步代码会阻塞后续代码的执行,直到当前任务完成。
异步代码是不按照顺序执行的代码,它会在后台执行,不会阻塞后续代码的执行。异步代码通常使用回调函数、Promise、async/await等方式来处理异步操作的结果。

通过异步执行,可以避免阻塞主线程,提高页面的响应性能。

Es6新特性

ES6的严格模式

  • 变量必须声明后在使用
  • 函数的参数不能有同名属性, 否则报错
  • 不能对只读属性赋值, 否则报错
  • 不能删除不可删除的数据, 否则报错
  • 禁止this指向全局对象
  • 增加了保留字(比如protected、static和interface)

let和const新增的变量声明

箭头函数、变量的解构赋值、反引号模板字符串

新增Symbol数据类型

Set 和 Map 数据结构

  • ES6 提供了新的数据结构 Set。它类似于数组,但是成员的值都是唯一的,没有重复的值。 Set 本身是一个构造函数,用来生成 Set 数据结构。
  • Map它类似于对象,也是键值对的集合,但是"键"的范围不限于字符串,各种类型的值(包括对象)都可以当作键

Proxy、Reflect

  • Proxy 可以理解成,在目标对象之前架设一层"拦截",外界对该对象的访问都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写
  • Reflect 反射对象,可以实现对 对象的增删改查

Promise

Promise 是异步编程的一种解决方案,比传统的解决方案------回调函数和事件------更合理和更强大

特点是:对象的状态不受外界影响。一旦状态改变,就不会再变,任何时候都可以得到这个结果

async 函数

async函数的返回值是 Promise 对象,这比 Generator 函数的返回值是 Iterator 对象方便多了; 可以用 then 方法指定下一步的操作

class

  • class跟let、const一样:不存在变量提升、不能重复声明等
  • ES6 的class可以看作只是一个语法糖,它的绝大部分功能ES5 都可以做到,新的class写法只是让对象原型的写法更加清晰、更像面向对象编程的语法而已。

Module

  • ES6 的模块自动采用严格模式,不管你有没有在模块头部加上 "use strict";
  • importexport 命令以及 exportexport default的区别

字符串的扩展

  • includes():返回布尔值,表示是否找到了参数字符串
  • startsWith():返回布尔值,表示参数字符串是否在原字符串的头部
  • endsWith():返回布尔值,表示参数字符串是否在原字符串的尾部

数值的扩展

  • Number.isFinite()用来检查一个数值是否为有限的(finite)。
  • Number.isNaN()用来检查一个值是否为NaN。
相关推荐
奋飞安全1 分钟前
初试js反混淆
开发语言·javascript·ecmascript
SameX10 分钟前
初识 HarmonyOS Next 的分布式管理:设备发现与认证
前端·harmonyos
M_emory_36 分钟前
解决 git clone 出现:Failed to connect to 127.0.0.1 port 1080: Connection refused 错误
前端·vue.js·git
Ciito40 分钟前
vue项目使用eslint+prettier管理项目格式化
前端·javascript·vue.js
成都被卷死的程序员1 小时前
响应式网页设计--html
前端·html
fighting ~1 小时前
react17安装html-react-parser运行报错记录
javascript·react.js·html
老码沉思录1 小时前
React Native 全栈开发实战班 - 列表与滚动视图
javascript·react native·react.js
abments1 小时前
JavaScript逆向爬虫教程-------基础篇之常用的编码与加密介绍(python和js实现)
javascript·爬虫·python
mon_star°2 小时前
将答题成绩排行榜数据通过前端生成excel的方式实现导出下载功能
前端·excel
Zrf21913184552 小时前
前端笔试中oj算法题的解法模版
前端·readline·oj算法