JS面试题

能来讲讲 JS 的语言特性吗

  运行在客户端浏览器上;
  不用预编译,直接解析执行代码; 是弱类型语言,较为灵活;
  与操作系统无关,跨平台的语言; 脚本语言、解释性语言

变量函数的提升,怎么提升至作用域顶部?
一个变量的生命周期
JS数据类型?

  8种。Number、String、Boolean、Null、undefined、object、symbol、bigInt。
  基本类型(单类型):String、Number、boolean、null、undefined。
  引用类型:object。里面包含的 function、Array、Date

怎么判断变量类型?之间的区别?

  typeof:typeof只能判断基本类型,不能判断引用类型;
  instanceof:用来测试一个对象在其原型链中是否存在一个构造函数的 prototype 属性;
  constructor:
  Object.prototype.toString.call():可以精准的判断对象类型,推荐
如何判断变量类型

如何判断一个数据是 NaN?

  NaN 非数字 但是用 typeof 检测是 number 类型
  a. 利用 NaN 的定义 用 typeof 判断是否为 number 类型并且判断是否满足 isNaN
  b. 利用 NaN 是唯一一个不等于任何自身的特点 n !== n
  c. 利用 ES6 中提供的 Object.is()方法(判断两个值是否相等)Object.is(n, NaN)

Js 中 null 与 undefined 区别

相同点:用 if 判断时,两者都会被转换成 false
不同点:
Number 转换的值不同: Number(null)为 0,而Number(undefined)为 NaN
Null 表示一个值被定义了,但是这个值是空值 Undefined 变量声明但未赋值

讲一下 TS ,有哪些关键字?

  interface、enum、class、原始数据类型(number、string、boolean、null、undefined、Symbol、Bigint)

箭头函数和普通函数的区别是什么?

  普通函数this:
    this总是代表它的直接调用者。
    在默认情况下,没找到直接调用者,this指的是window。
    在严格模式下,没有直接调用者的函数中的this是undefined。
    使用call,apply,bind绑定,this指的是绑定的对象。
  箭头函数this:
    在使用=>定义函数的时候,this的指向是 定义时所在的对象,而不是使用时所在的对象;
    不能够用作构造函数,这就是说,不能够使用new命令,否则就会抛出一个错误;
    不能够使用 arguments 对象;
    不能使用 yield 命令;

JS 的 new 操作符做了哪些事情

  new 操作符新建了一个空对象,这个对象原型指向构造函数的 prototype,执行构造函数后返回这个对象。

call、apply、bind三者的用法和区别

  通过apply和call改变函数的this指向,他们两个函数的第一个参数都是一样的表示要改变指向的那个对象,第二个参数,apply是数组,而call则是arg1,arg2...这种形式。
  通过bind改变this作用域会返回一个新的函数,这个函数不会马上执行。
call、apply、bind三者的用法和区别

讲一下let、var、const的区别

  var 没有块级作用域,支持变量提升。
  let 有块级作用域,不支持变量提升。不允许重复声明,暂存性死区。不能通过 window.变量名 进行访问.
  const 有块级作用域,不支持变量提升,不允许重复声明,暂存性死区。声明一个变量一旦声明就不能改变,改变报错。

闭包是什么?有什么特性?对页面会有什么影响

  闭包可以简单理解成:定义在一个函数内部的函数。其中一个内部函数在包含它们的外部函数之外被调用时,就会形成闭包。
特点:
  1. 函数嵌套函数。
  2. 函数内部可以引用外部的参数和变量。
  3. 参数和变量不会被垃圾回收机制回收。
使用:
  1. 读取函数内部的变量;
  2. 这些变量的值始终保持在内存中,不会在外层函数调用后被自动清除。
优点:
  1. 变量长期驻扎在内存中;
  2. 避免全局变量的污染;
  3. 私有成员的存在;
缺点:
  会造成内存泄露

Js 中常见的内存泄漏
  1. 意外的全局变量
  2. 被遗忘的计时器或回调函数
  3. 脱离 DOM 的引用
  4. 闭包
事件委托是什么?如何确定事件源(Event.target 谁调用 谁就是事件源)

JS 高程上讲:事件委托就是利用事件冒泡,只制定一个时间处理程序, 就可以管理某一类型的所有事件。
  事件委托,称事件代理,是 js 中很常用的绑定事件的技巧,事件委托就是把原本需要绑定在子元素的响应事件委托给父元素,让父元素担当事件监听的职务,事件委托的原理是 DOM 元素的事件冒泡

什么是事件冒泡?

  一个事件触发后,会在子元素和父元素之间传播,这种传播分为三个阶段:
    捕获阶段 —— 从 window 对象传导到目标节点(从外到里),这个阶段不会响应任何事件,
    目标阶段 —— 在目标节点上触发,
    冒泡阶段 —— 从目标节点传导回 window 对象(从里到外),
事件委托/事件代理就是利用事件冒泡的机制把里层需要响应的事件绑定到外层

本地存储与 cookie 的区别
数组方法有哪些请简述
  • push() 从后面添加元素,返回值为添加完后的数组的长度
  • arr.pop() 从后面删除元素,只能是一个,返回值是删除的元素
  • arr.shift() 从前面删除元素,只能删除一个 返回值是删除的元素
  • arr.unshift() 从前面添加元素, 返回值是添加完后的数组的长度
  • arr.splice(i,n) 删除从 i(索引值)开始之后的那个元素。返回值是删除的元素
  • arr.concat() 连接两个数组 返回值为连接后的新数组
  • str.split() 将字符串转化为数组
  • arr.sort() 将数组进行排序,返回值是排好的数组,默认是按照最左边的数 字进行排序,不是按照数字大小排序的
  • arr.reverse() 将数组反转,返回值是反转后的数组
  • arr.slice(start,end) 切去索引值 start 到索引值 end 的数组,不包含 end 索引的值,返回值是切出来的数组
  • arr.forEach(callback) 遍历数组,无 return 即使有 return,也不会返回 任何值,并且会影响原来的数组
  • arr.map(callback) 映射数组(遍历数组),有 return 返回一个新数组
  • arr.filter(callback) 过滤数组,返回一个满足要求的数组
  • arr.flat() 扁平化
普通函数和构造函数的区别

  1. 构造函数也是一个普通函数,创建方式和普通函数一样,但是构造函数习惯上首字母大写
  2. 调用方式不一样,普通函数直接调用,构造函数要用关键字 new 来调用
  3. 调用时,构造函数内部会创建一个新对象,就是实例,普通函数不会创建新对象
  4. 构造函数内部的 this 指向实例,普通函数内部的 this 指向调用函数的对象(如果没有对象调用,默认为 window)
  5. 构造函数默认的返回值是创建的对象(也就是实例),普通函数的返回值由 return 语句决定
  6. 构造函数的函数名与类名相同

简述深浅拷贝

浅拷贝——通常需要拷贝的对象内部只有一层的这种对象。
常用的方法
  Object.assign方法来实现
  扩展运算符 ...obj
深拷贝——通常是嵌套二层或以上的复杂对象
常用方法
  JSON.parse(JSON.stringfy(object)); 该方法忽略掉undefined、忽略Symbol、忽略function。只适合简单深拷贝 关于JSON.parse(JSON.stringify(obj))实现深拷贝应该注意的坑
  手写递归方法去实现。
  通过第三方库提供的深拷贝实现(lodash)。

// 递归
function cloneObject(obj) {
  var newObj = {} //如果不是引用类型,直接返回
  if (typeof obj !== 'object' && obj === null) {
    return obj
  }
  //如果是引用类型,遍历属性
  else {
    for (var attr in obj) {
      //如果某个属性还是引用类型,递归调用
      newObj[attr] = cloneObject(obj[attr])
    }
  }
  return newObj
}
Promise 的理解

  什么是 Promise?
    我们都知道,Promise 是承诺的意思,承诺它过一段时间会给你一个结 果。Promise 是一种解决异步编程的方案,相比回调函数和事件更合理和更 强大。 从语法上讲,promise 是一个对象,从它可以获取异步操作的消息;
  promise 有三种状态:
    pending 初始状态也叫等待状态,fulfiled 成功状态,rejected 失败状态;状态一旦改变,就不会再变。创造 promise 实例后,它会立即执行。
  Promise 的两个特点
    1、Promise 对象的状态不受外界影响
    2、Promise 的状态一旦改变,就不会再变,任何时候都可以得到这个结 果,状态不可以逆
  Promise 的三个缺点
    1、无法取消 Promise,一旦新建它就会立即执行,无法中途取消
    2、如果不设置回调函数,Promise 内部抛出的错误,不会反映到外部
    3、当处于 pending(等待)状态时,无法得知目前进展到哪一个阶段, 是刚刚开始还是即将完成

我们用 Promise 来解决什么问题?

  promise 是用来解决两个问题的:
    1. 回调地狱,代码难以维护, 常常第一个的函数的输出是第二个函数的 输入这种现象
    2. promise 可以支持多并发的请求,获取并发请求中的数据
这个 promise 可以解决异步的问题,本身不能说 promise 是异步的

请简述 async 的用法

  async 就是 generation 和 promise 的语法糖,async 就是将 generator 的*换成 async,将 yiled 换成 await
  函数前必须加一个 async,异步操作方法前加一个 await 关键字,意思就是等一下,执行完了再继续走,注意:await 只能在 async 函数中运行, 否则会报错
  Promise 如果返回的是一个错误的结果,如果没有做异常处理,就会报错,所以用 try..catch 捕获一下异常就可以了

async/await 如何通过同步的方式实现异步

  async/await 是一个自执行的 generate 函数。利用 generate 函数的特性把异步的代码写成“同步”的形式。

var fetch = require("node-fetch");
function *gen() { // 这里的 * 可以看成
  async var url = "https://api.github.com/users/github";
  var result = yield fetch(url); // 这里的 yield 可以看成 await
  console.log(result.bio);
}
var g = gen();
var result = g.next();
result.value
  .then(data => data.json())
  .then(data => g.next(data));
setTimeout、Promise、Async/Await 的区别

  setTimeout: setTimeout 的回调函数放到宏任务队列里,等到执行栈清空以后执行;
  Promise: Promise 本身是同步的立即执行函数,当在 executor 中执行 resolve 或者 reject 的时候,此时是异步操作,会先执行 then/catch 等,当主栈完成 时,才会去调用 resolve/reject 方法中存放的方法。
  async: async 函数返回一个 Promise 对象,当函数执行的时候,一旦遇到 await 就 会先返回,等到触发的异步操作完成,再执行函数体内后面的语句。可以理解为, 是让出了线程,跳出了 async 函数体。

简述一下 Generator 函数

  传统的编程语言,早有异步编程的解决方案(其实是多任务的解决方案)。其中 有一种叫做“协程”(coroutine),意思是多个线程互相协作,完成异步任务。 协程有点像函数,又有点像线程,它的运行流程大致如下:

  • 第一步,协程 A 开始执行;
  • 第二步,协程 A 执行到一半,进入暂停,执行权转移到协程 B;
  • 第三步,(一段时间后)协程 B 交还执行权;
  • 第四步,协程 A 恢复执行
    上面流程的协程 A,就是异步任务,因为它分成两段(或多段)执行。 举例来说,读取文件的协程写法如下:
function* asyncJob() { // ...
  var f = yield readFile(fileA);
  // ...
}

上面代码的函数 asyncJob 是一个协程,它的奥妙就在其中的 yield 命令。它表示执行到此处,执行权将交给其他协程。也就是说,yield 命令是异步两个阶段的分界线。协程遇到 yield 命令就暂停,等到执行权返回,再从暂停的地方继续往后执行。
  Generator 函数是协程在 ES6 的实现,最大特点就是可以交出函数的执行权(即 暂停执行)。

function* gen(x) { var y = yield x + 2; return y;}
var g = gen(1);
g.next() // { value: 3, done: false }g.next(2) // { value: 2, done: false }

next 是返回值的 value 属性,是 Generator 函数向外输出数据;next 方法还可以 接受参数,向 Generator 函数体内输入数据。
上面代码中,第一个 next 方法的 value 属性,返回表达式 x + 2 的值 3。第二 个 next 方法带有参数 2,这个参数可以传入 Generator 函数,作为上个阶段 异步任务的返回结果,被函数体内的变量 y 接收。因此,这一步的 value 属性,返回的就是 2(变量 y 的值)。

请简述原型/原型链/(原型)继承

什么是原型
  任何对象实例都有一个原型,也叫原型对象,这个原型对象由对象的内置属性_proto_指向它的构造函数的 prototype 指向的对象,即任何对象都是由一个构造函数创建的,但是不是每一个对象都有 prototype,只有方法才有 prototype。
什么是原型链?
  原型链基本思想是利用原型让一个引用类型继承另一个引用类型的属性和方法。我们知道,每个构造函数都有一个原型对象,每个原型对象都有一个指向构造函数的指针,而实例又包涵一个指向原型对象的内部指针。
  原型链的核心就是依赖对象的_proto_的指向,当自身不存在的属性时,就一层层的扒出创建对象的构造函数,直至到 Object 时,就没有 _proto_指向了。 因为_proto_实质找的是 prototype,所以我们只要找这个链条上的构造函数的 prototype。其中 Object.prototype 是没有_proto_属性的,它 ==null。
  每个构造函数都有一个原型对象,原型对象都包含一个指向构造函数的指针,而实例都包含指向原型对象内部的指针。我们让原型对象(1)等于另一个原型对象的实例(2), 此时原型对象(2)将包含一个指向原型对象(1)的指针, 再让原型对象(2)的实例等于原型对象(3),如此层层递进就构成了实例和原型的链条,这就是原型链的概念。
  每个构造函数都有一个原型对象,原型对象都包含一个指向构造函数想指针(constructor),而实例对象都包含一个指向原型对象的内部指针 (__proto__)。如果让原型对象等于另一个原型对象的实例,此时的原型 对象将包含一个指向另一个原型的指针(__proto__),另一个原型也包含着一个指向另一个构造函数的指针(constructor)。假如另一个原型又是另一个类型的实例……这就构成了实例与原型的链条。也叫原型链
  原型继承是 js 的一种继承方式,原型链作为实现继承的主要方法,其基本思路是利用原型让一个引用类型继承另一个引用类型的属性和方法,原型继承:利用原型中的成员可以被和其相关的对象共享这一特性,可以实现继承,这种实现继承的方式,就叫做原型继承.

从浏览器地址栏输入URL到显示网页,这个过程是怎么实现的

  a、进行域名解析,找出对应的IP地址
  b、浏览器和服务器进行TCP连接,建立三次握手
    客户端发起请求到服务器,等待服务器响应,第一次握手;
    服务器接收到客户端信息,并发送结果至客户端,第二次握手;
    客户端收到结果,并发送服务器确认,第三次握手
  c、握手成功之后,浏览器会把请求信息发送服务器,服务器把响应资源返回给浏览器
  d、浏览器加载资源,继续请求资源中所需的js、css、图片等额外资源,形成结构树,渲染树,计算布局
  e、将资源渲染呈现给用户

为什么会造成跨域/请简述同源策略

  出现跨域问题的原因:
    在前后端分离的模式下,前后端的域名是不一致的,此时就会发生跨域访问问题。在请求的过程中我们获取数据一般都是 post/get 请求, 所以..跨域问题出现
  跨域问题来源于 JavaScript 的同源策略,即只有 协议+主机名+端口号 (如存在)相同,则允许相互访问。也就是说 JavaScript 只能访问和操作自己域下的资源,不能访问和操作其他域下的资源。
  同源策略 是由 NetScape 提出的一个著名的安全策略。所谓的同源,指的是协议,域名,端口相同。浏览器处于安全方面的考虑,只允许本域名下的接口交互,不同源的客户端脚本,在没有明确授权的情况下,不能读写对方的资源。

解决跨域的方法
  1. JSONP
  2. CORS(Cross-Origin-Resource-Share,跨域资源共享),由服务端设置响应头通过浏览器的同源策略限制
      a. Access-Control-Allow-Origin: *;
      b. Access-Control-Allow-Methods: *;
      c. Access-Control-Allow-Headers: *;
      d. Access-Control-Allow-Credentials: true;
  3. nginx代理跨域
  4. nodejs中间件代理跨域
    前端常见跨域解决方案(全)
['1', '2', '3'].map(parseInt) what & why ?

  ['1', '2', '3'].map(parseInt) 的输出结果为 [1, NaN, NaN]。
  因为 parseInt(string, radix) 将一个字符串 string 转换为 radix 进制的整数, radix 为介于 2-36 之间的数。
  在数组的 map 方法的回调函数中会传入 item(遍历项) 和 index(遍历下标) 作为前两个参数,所以这里的 parseInt 执行了对应的三次分别是
    parseInt(1,0)
    parseInt(2, 1)
    parseInt(3, 2)
  因此对应的执行结果分别为 1、NaN、NaN。

将数组扁平化并去除其中重复数据,最终得到一个升序且不重复的数组
Array.from(new Set(arr.flat(Infinity))).sort((a,b)=>{ return a-b })

es6之数组的flat(),flatMap()

介绍下Set、Map、WeakSet 和 WeakMap 的区别?

  Set
    a. 成员不能重复;
    b. 只有键值,没有键名,有点类似数组;
    c. 可以遍历,方法有add、delete、has;
  WeakSet
    a. 成员都是对象(引用);
    b. 成员都是弱引用,随时可以消失(不计入垃圾回收机制)。可以用来保存 DOM 节点,不容易造成内存泄露;
    c. 不能遍历,方法有 add、delete、has;
  Map
    a. 本质上是键值对的集合,类似集合;
    b. 可以遍历,方法很多,可以跟各种数据格式转换;
    Map对象
  WeakMap
    a. 只接收对象为键名(null 除外),不接受其他类型的值作为键名;
    b. 键名指向的对象,不计入垃圾回收机制;
    c. 不能遍历,方法同 get、set、has、delete;

数组去重?
什么是防抖和节流?有什么区别? 如何实现?

  防抖:动作绑定事件,动作发生后一定时间后触发事件,在这段时间内,如果该动作又发生,则重新等待一定时间再触发事件。

function debounce(fn, time) {
    let timeout = null; // 创建一个标记用来存放定时器的返回值
    return function () {
        clearTimeout(timeout); // 每当用户输入的时候吧前一个 setTimeout clear 掉
        timeout = setTimeout(() => {
            // 然后又创建一个新的 setTimeout,这样就能保证输入字符后的 interval 
            // 间隔内如果还有字符输入的话,就不会执行 fn 函数
            fn.apply(this, arguments)
        }, time);
    }
}

  节流: 动作绑定事件,动作发生后一段时间后触发事件,在这段时间内,如果动作又发生,则无视该动作,直到事件执行完后,才能重新触发。

function throttle(fn, time) {
    let canRun = true; // 通过闭包保存一个标记
    return function () {
        if (!canRun) return; // 在函数开头判断标记是否为 true,不为 true 则 return
        canRun = false; // 立即设置为 false
        setTimeout(() => { // 将外部传入的函数的执行放在 setTimeout 中
            fn.apply(this, arguments);
            // 最后在 setTimeout 执行完毕后再把标记设置为 true(关键) 表示可以执行下一次循环了。
            // 当定时器没有执行的时候标记永远是 false,在开头被 return 掉
            canRun = true;
        }, time);
    }
}
写 React / Vue 项目时为什么要在列表组件中写 key,其作用是什么?

  vue 和 react 都是采用 diff 算法来对比新旧虚拟节点,从而更新节点。在 vue 的 diff 函数交叉对比中,当新节点跟旧节点头尾交叉对比没有结果时,会根据新节点的 key 去对比旧节点数组中的 key,从而找到相应旧节点(这里对应的是一个 key => index 的 map 映射)。如果没有找到就认为是一个新增节点。而如果没有 key,那么就会采用遍历查找的方式去找到对应的旧节点。一种一个 map 映射, 另一种是遍历查找。相比而言,map 映射的速度更快。

对前端工程化的理解


image.png

你可能感兴趣的:(JS面试题)