前端面试题-Javascript

205 阅读14分钟

1.JavaScript 的数据类型有哪些?

  1. 基本类型:(NumberStringBooleannullundefinedsymbol
  2. 引用类型:主要(Object、Array、Function)

2.== 和 === 区别,分别在哪种情况下使用?

区别:

  1. 相等操作符(==)会先进行类型转换,然后再进行值的比较,全等操作符(===)不会进行类型的转换。
  2. nullundefined 比较,相等操作符返回 true,全等操作符返回 false

3.typeof 与 instanceof 的区别?

  1. typeof 操作符返回一个字符串,表示变量的类型。
typeof 1 // 'number' 
typeof '1' // 'string' 
typeof undefined // 'undefined' 
typeof true // 'boolean' 
typeof Symbol() // 'symbol' 
typeof null // 'object' 
typeof [] // 'object' 
typeof {} // 'object' 
typeof console // 'object' 
typeof console.log // 'function'
  1. instanceof 操作符用于检测变量是否出现在对象的原型链上面。

区别:

  1. typeof 返回的是变量类型的字符串,instanceof 返回的是一个布尔值。
  2. instanceof 可以判断复杂的引用类型,但是没办法正确判断基本数据类型。
  3. typeof 可以判断基本数据类型(null 除外),无法准确判断引用数据类型(function 除外)。

如果需要检测变量的数据类型,可以通过 Object.prototype.toString.call 方法,调用该方法统一返回格式 [object Xxx] 的字符串。

Object.prototype.toString.call({}) // "[object Object]" 
Object.prototype.toString.call(1) // "[object Number]" 
Object.prototype.toString.call('1') // "[object String]" 
Object.prototype.toString.call(true) // "[object Boolean]"
Object.prototype.toString.call(function(){}) // "[object Function]"
Object.prototype.toString.call(null)  //"[object Null]"
Object.prototype.toString.call(undefined) //"[object Undefined]" 
Object.prototype.toString.call(/123/g) //"[object RegExp]" 
Object.prototype.toString.call(new Date()) //"[object Date]" 
Object.prototype.toString.call([]) //"[object Array]" 
Object.prototype.toString.call(document) //"[object HTMLDocument]" 
Object.prototype.toString.call(window) //"[object Window]"

使用此方法,我们可以封装一个全局通用的类型判断方法:

function getType(obj){ 
    let type = typeof obj; 
    if (type !== "object") { // 先进行 typeof 判断,如果是基本数据类型直接返回
        return type; 
    }
    // 对于 typeof 返回的 object 的,在进行以下的判断,通过正则返回结果。
    return Object.prototype.toString.call(obj).replace(/^\[object (\S+)\]$/, '$1'); 
}

4.JavaScript 原型和原型链?

原型.png

总结:

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

5.说说你对作用域链的理解?

  • 等待整理

6.谈谈你对 this 对象的理解?

  1. 全局对象中的指向:指向的是 window
  2. 全局作用域或者普通函数中的 this:指向全局的 window
  3. this 永远指向最后调用它的那个对象,不是在箭头函数的情况下
  4. new 关键词改变了this的指向
  5. applycallbind:改变 this 的指向,不是箭头函数的情况下
  6. 箭头函数中的 this:箭头函数本身没有 this,看外层是否有函数,有就是函数的 this,没有就是 window
  7. 匿名函数中的 this:永远指向 window,匿名函数的执行环境具有全局性,因此 this 指向 window

7.说说 new 操作付具体干了什么?

  • 创建一个新的对象 obj
  • 将对象与构造函数(constructor)通过原型链连接起来
  • 将构建函数中的 this 绑定到新建的对象 obj
  • 根据构建函数返回类型作判断,如果是原始值则被忽略,如果是返回对象,需要正常处理

8.bind、call、apply 区别?

bind、call、apply 的调用方式

fun.call(thisArg, param1, param2, ...)
fun.apply(thisArg, [param1,param2,...])
fun.bind(thisArg, param1, param2, ...)

参数

thisArg(可选):

  1. funthis指向thisArg对象
  2. 非严格模式下:thisArg 指定为 nullundefinedfun 中的 this 指向 window 对象.
  3. 严格模式下:funthisundefined
  4. 值为原始值(数字,字符串,布尔值)的 this 会指向该原始值的自动包装对象,如 String、Number、Boolean

param1,param2(可选): 传给fun的参数。

  1. 如果 param 不传或为 null/undefined,则表示不需要传入任何参数.
  2. apply 第二个参数为数组,数组内的值为传给fun的参数。

call/apply与bind的区别

执行

  • call/apply 改变了函数的 this 上下文后马上执行该函数
  • bind 则是返回改变了上下文后的函数,不执行该函数

返回值:

  • call/apply 返回fun的执行结果
  • bind 返回fun的拷贝,并指定了fun的this指向,保存了fun的参数。

call与apply的唯一区别

传给fun 的参数写法不同:

  • apply 是第2个参数,这个参数是一个数组:传给fun参数都写在数组中。
  • call 从第2~n的参数都是传给fun的。

9.JavaScript 中执行上下文和执行栈是什么?

  • 等待整理

10.说说 JavaScript 中的事件模型?

  • 等待整理

11.解释下什么是事件代理?

  • 等待整理

12.说说你对闭包的理解?

一个函数和对其周围状态(lexical environment,词法环境)的引用捆绑在一起(或者说函数被引用包围)这样的组合就是闭包(closure)

也就是说,闭包让你可以在一个内层函数中访问到其外层函数的作用域。

闭包的3个特性

  1. 函数嵌套函数
  2. 函数内部可以引用函数外部的参数和变量
  3. 参数和变量不会被垃圾回收机制回收

闭包的优点

  • 保护函数内的变量安全 ,实现封装,防止变量流入其他环境发生命名冲突
  • 在内存中维持一个变量,可以做缓存(但使用多了同时也是一项缺点,消耗内存)
  • 匿名自执行函数可以减少内存消耗

闭包的缺点

  • 其中一点上面已经有体现了,就是被引用的私有变量不能被销毁,增大了内存消耗,造成内存泄漏,解决方法是可以在使用完变量后手动为它赋值为null
  • 其次由于闭包涉及跨域访问,所以会导致性能损失,我们可以通过把跨作用域变量存储在局部变量中,然后直接访问局部变量,来减轻对执行速度的影响

闭包的使用场景

  • 创建私有变量
  • 延长变量的生命周期

13.谈谈 JavaScript 中的类型转换机制?

  • 等待整理

14.深拷贝和浅拷贝的区别?

赋值与深/浅拷贝的区别

  • 赋值操作符是把一个对象的引用赋值给一个变量,所以变量中存储的是对象的引用
  • 浅拷贝是拷贝源对象的每个属性,但如果属性值是对象,那么拷贝的是这个对象的引用。所以源对象和拷贝对象之间共享嵌套对象。
  • 深拷贝与浅拷贝不同的地方在于,如果属性值为对象,那么会拷贝该对象。源对象和拷贝对象之间不存在共享的内容。

浅拷贝

浅拷贝,指的是创建新的数据,这个数据有着原始数据属性值的一份精确拷贝如果属性是基本类型,拷贝的就是基本类型的值。如果属性是引用类型,拷贝的就是内存地址即浅拷贝是拷贝一层,深层次的引用类型则共享内存地址。

浅拷贝的方法有哪些

  • Object.create
  • Object.assign
  • Array.prototype.slice()Array.prototype.concat()
  • 拓展运算符(...

注意:拓展运算符(...)只有第一层是深拷贝,第二层及更深层是浅拷贝。

手写浅拷贝

function shallowCopy(obj) {
  if (typeof obj !== 'object' || obj !== null) return;
  let objCopy = obj instanceof Array ? [] : {};
  for (let key in obj) { // for...in 可以用于对象中的遍历,遍历出来的是对象的 key 值,在数组中是数组的下标
    if (obj.hasOwnProperty(key)) { // 不需要obj隐式原型上的属性
      objCopy[key] = obj[key];
    }
  }
  return objCopy;
}

深拷贝

深拷贝是开辟一个新的栈,两个对象属完成相同,但是对应两个不同的地址,修改一个对象的属性,不会改变另一个对象的属性。

深拷贝的方法有哪些

  • _.cloneDeep() (Lodash 的方法)
  • jQuery.extend()
  • JSON.stringify(JSON.parse(xx))
  • 手写递归循环

注意JSON.stringify 的方式存在弊端,会忽略 undefinedsymbol函数RegExp

手写深拷贝

function deepCopy(obj) {
  let objCopy = obj instanceof Array ? [] : {}; // 创建一个空对象,用于存放拷贝后的对象
  for (let key in obj) {
    // 遍历对象的所有属性
    if (obj.hasOwnProperty(key)) {
      // 使用 hasOwnProperty 方法确保只拷贝对象自身的属性
      if (obj[key] instanceof Object || obj[key] instanceof Array) {
        // 判断属性值是否为引用类型
        objCopy[key] = deepCopy(obj[key]);
      } else {
        objCopy[key] = obj[key]; // 如果是原始类型,则直接赋值
      }
    }
  }
  return objCopy;
}

15.JavaScript 字符串的常用方法有哪些?

concat(str1, str2, /* …, */ strN)
slice(indexStart, indexEnd)
substr(start, length) // 已废弃
substring(indexStart, indexEnd)
trim()
trimLeft()
trimRight()
replace(pattern, replacement)
includes(searchString, position
split(separator, limit)
// ……

16.数组常用方法有哪些?

push(element0, element1, /* … ,*/ elementN)
unshift(element1, element2, /* …, */ elementN)
concat(value0, value1, /* … ,*/ valueN)
splice(start, deleteCount, item1, item2, itemN)
pop()
shift()
slice(start, end)
find(callbackFn, thisArg)
findIndex(callbackFn, thisArg)
includes(searchElement, fromIndex)
// ……

17.说说你对事件循环的理解?

在 JavaScript 中,所有任务都可以分为

  • 同步任务:立即执行的任务,同步任务一般会直接进入到主线程中执行
  • 异步任务:异步执行的任务,比如 Ajax 网络请求,setTimeout 定时函数等。

宏任务与微任务

在 JavaScript 中,异步任务还可以细分为 宏任务微任务

微任务

一个 微任务 的执行时机是在主函数执行结束之后、当前宏任务结束之前

宏任务

宏任务 的时间粒度比较大,执行的时间间隔是不能精确控制的,对一些高实时性的需求就不太符合

常见的宏任务与微任务

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

JS中微任务和宏任务执行顺序

  1. 首先执行当前代码(同步任务),直到遇到第一个宏任务或微任务。
  2. 如果遇到微任务,则将它添加到微任务队列中,继续执行同步任务。
  3. 如果遇到宏任务,则将它添加到宏任务队列中,继续执行同步任务。
  4. 当前任务执行完毕后,JavaScript 引擎会先执行所有微任务队列中的任务,直到微任务队列为空。
  5. 然后执行宏任务队列中的第一个任务,直到宏任务队列为空。
  6. 重复步骤 4 和步骤 5,直到所有任务都被执行完毕。 需要注意的是,微任务比宏任务优先级要高,因此在同一个任务中,如果既有微任务又有宏任务,那么微任务会先执行完毕。而在不同的任务中,宏任务的执行优先级要高于微任务,因此在一个宏任务执行完毕后,它才会执行下一个宏任务和微任务队列中的任务。

举个例子,假设当前代码中有一个 setTimeout 和一个 Promise,它们分别对应一个宏任务和一个微任务。

那么执行顺序如下:

  1. 执行当前代码,将 setTimeout 和 Promise 添加到宏任务和微任务队列中。
  2. 当前任务执行完毕,JavaScript 引擎先执行微任务队列中的 Promise 回调函数。
  3. 微任务队列为空后,再执行宏任务队列中的 setTimeout 回调函数。

需要注意的是,在一些特殊情况下,微任务和宏任务的执行顺序可能会发生变化,比如在使用 MutationObserver 监听 DOM 变化时,它会被视为一个微任务,但是它的执行顺序可能会比其他微任务更靠后。因此,需要根据具体情况来理解和处理微任务和宏任务的执行顺序。

思考题

console.log(0);
function a() {
  console.log(1);
  new Promise((resolve, reject) => {
    console.log(2);
    resolve();
  })
    .then(() => {
      console.log(3);
    })
    .then(() => {
      console.log(4);
    });
  console.log(5);
  setTimeout(() => {
    console.log(6);
  }, 50);
}
async function b() {
  console.log(7);
  await console.log(8);
  new Promise((resolve, reject) => {
    console.log(9);
    resolve();
  })
    .then(() => {
      console.log(10);
    })
    .then(() => {
      console.log(11);
    });
  await console.log(12);
  console.log(13);
  setTimeout(() => {
    console.log(14);
  }, 10);
}

a();
console.log(15);
b();
console.log(16);
// 输出结果是什么?

18.JavaScript 本地存储的方式有哪些?

  • cookie
  • localStorage
  • sessionStorage
  • indexedDB

关于 cookie 、sessionStorage 、 localStorage 三者的区别

  • 存储大小:cookie 数据大小不能超过 4ksessionStoragelocalStorage 虽然也有存储大小的限制,但比 cookie 大得多,可以达到 5M 或更大
  • 有效时间:localstorage 存储持久数据,浏览器关闭后数据不丢失除非主动删除数据;sessionStorage 数据在当前浏览器窗口关闭后自动删除; cooke 设置的 cookie 过期时间之前一直有效,即使窗口或浏览器关闭
  • 数据与服务器之间的交互方式,cookie 的数据会自动的传递到服务器,服务器端也可以写 cookie 到客户端; sessionStoragelocalstorage 不会自动把数据发给服务器,仅在本地保存

19.ajax 原理是什么?

AJAX 全称(Async Javascript and XML)

即异步的 JavaScriptXML,是一种创建交互式网页应用的网页开发技术,可以在不重新加载整个网页的情况下,与服务器交换数据,并且更新部分网页

Ajax 的原理简单来说通过 XmlHttpRequest 对象来向服务器发异步请求,从服务器获得数据,然后用 JavaScript 来操作 DOM 而更新页面

20.什么是防抖和节流?

防抖和节流本质上是优化高频率执行代码的一种手段。

  • 节流:n 秒内只运行一次,若在 n 秒内重复触发,只有一次生效
  • 防抖:n 秒后在执行该事件,若在 n 秒内被重复触发,则重新计时

节流的代码实现

function throttled(fn, delay) {
  let timer = null;
  let starttime = Date.now();
  return function () {
    let curTime = Date.now(); // 当前时间
    let remaining = 0;
    if (curTime - starttime > delay) {
      remaining = delay;
      starttime = curTime; // 重置开始时间
    } else {
      remaining = delay - (curTime - starttime); // 从上一次到现在,还剩多少多余时间
    }
    let context = this;
    let args = arguments;
    clearTimeout(timer);
    if (remaining <= 0) {
      fn.apply(context, args);
      starttime = Date.now();
    } else {
      timer = setTimeout(fn, remaining);
    }
  };
}

防抖代码实现

function debounce(func, wait, immediate) {
  let timeout;
  return function () {
    let context = this;
    let args = arguments;
    if (timeout) clearTimeout(timeout); // timeout 不为 null
    if (immediate) {
      let callNow = !timeout; //第一次会立即执行,以后只有事件执行后才会再次触发
      timeout = setTimeout(function () {
        timeout = null;
      }, wait);
      if (callNow) {
        func.apply(context, args);
      }
    } else {
      timeout = setTimeout(function () {
        func.apply(context, args);
      }, wait);
    }
  };
}

区别

相同点:

  • 都可以通过使用 setTimeout 实现
  • 目的都是,降低回调执行频率。节省计算资源

不同点:

  • 函数防抖,在一段连续操作结束后,处理回调,利用 clearTimeoutsetTimeout 实现。函数节流,在一段连续操作中,每一段时间只执行一次,频率较高的事件中使用来提高性能
  • 函数防抖关注一定时间连续触发的事件,只在最后执行一次,而函数节流一段时间内只执行一次

例如,都设置时间频率为500ms,在2秒时间内,频繁触发函数,节流,每隔 500ms 就执行一次。防抖,则不管调动多少次方法,在2s后,只会执行一次

21.web 常见的攻击方式有哪些?

  • 等待整理

22.说说 JavaScript 中内存泄漏的几种情况?

内存泄漏(Memoryleak)是在计算机科学中,由于疏忽或错误造成程序未能释放已经不再使用的内存

并非指内存在物理上的消失,而是应用程序分配某段内存后,由于设计错误,导致在释放该段内存之前就失去了对该段内存的控制,从而造成了内存的浪费

程序的运行需要内存。只要程序提出要求,操作系统或者运行时就必须供给内存

对于持续运行的服务进程,必须及时释放不再用到的内存。否则,内存占用越来越高,轻则影响系统性能,重则导致进程崩溃。

常见的可能导致内存泄漏的方式

  • 闭包
  • 定时器 (setInterval
  • 死循环
  • 递归
  • 未清理 DOM 元素的引用

23.JavaScript 如何实现继承?

  • 等待整理

24.说说 JavaScript 数字精度丢失问题,如何解决?

  • 等待整理

25.举例说明你对尾递归的理解,有哪些应用场景?

  • 等待整理