面试题 js

267 阅读14分钟

1.判断 js 类型的方式

  • typeof 可以判断出'string','number','boolean','undefined','symbol',但判断 typeof(null) 时值为 'object'; 判断数组和对象时值均为 'object'

  • instanceof 原理是 构造函数的 prototype 属性是否出现在对象的原型链中的任何位置

  • Object.prototype.toString.call() 常用于判断浏览器内置对象,对于所有基本的数据类型都能进行判断,即使是 null 和 undefined

  • Array.isArray() 用于判断是否为数组

2.字符串操作函数

  • toString() – 可以将任何类型的数据都转换为字符串。

  • concat() – 将两个或多个字符的文本组合起来,返回一个新的字符串。

  • indexOf() – 返回字符串中一个子串第一处出现的索引。如果没有匹配项,返回 -1 。

  • charAt() – 返回指定位置的字符。

  • lastIndexOf() – 返回字符串中一个子串最后一处出现的索引,如果没有匹配项,返回 -1 。

  • match() – 检查一个字符串是否匹配一个正则表达式。

  • substr() 函数 -- 起始位置开始截取,结束位置为第二个参数截取的字符串最大长度。

  • substring() – 返回字符串的一个子串。传入参数是起始位置和结束位置。

  • slice() – 提取字符串的一部分,并返回一个新字符串。

  • replace() – 用来查找匹配一个正则表达式的字符串,然后使用新字符串代替匹配的字符串。

  • search() – 执行一个正则表达式匹配查找。如果查找成功,返回字符串中匹配的索引值。否则返回 -1 。

  • split() – 通过将字符串划分成子串,将一个字符串做成一个字符串数组。

  • length – 返回字符串的长度,所谓字符串的长度是指其包含的字符的个数。

  • toLowerCase() – 将整个字符串转成小写字母。

  • toUpperCase() – 将整个字符串转成大写字母。

3.Array 相关的属性和方法

  • includes() – 找到匹配项返回布尔值,不能识别 undefined 和 NaN 。

  • concat() 连接两个或更多的数组,并返回结果。

  • join() 把数组的所有元素放入一个字符串。元素通过指定的分隔符进行分隔。

  • pop() 删除并返回数组的最后一个元素。

  • shift() 删除并返回数组的第一个元素

  • push() 向数组的末尾添加一个或更多元素,并返回新的长度。

  • unshift() 向数组的开头添加一个或更多元素,并返回新的长度。

  • reverse() 颠倒数组中元素的顺序。

  • slice() 从某个已有的数组返回选定的元素

  • sort() 对数组的元素进行排序

  • splice() 删除元素,并向数组添加新元素。

  • toSource() 返回该对象的源代码。

  • toString() 把数组转换为字符串,并返回结果。

  • toLocaleString() 把数组转换为本地数组,并返回结果。

  • valueOf() 返回数组对象的原始值

4.闭包的概念?优缺点?

闭包的概念:闭包就是能读取其他函数内部变量的函数。

优点:

  • 避免全局变量的污染

  • 希望一个变量长期存储在内存中(缓存变量)

缺点:

  • 内存泄露(消耗)

  • 常驻内存,增加内存使用量

5.浅拷贝和深拷贝

浅拷贝

  • Object.assign()

  • Array.prototype.slice()

  • 扩展运算符 ...

深拷贝

  • JSON.parse(JSON.stringify())

  • 递归函数

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

6.数组去重的方法

ES6 的 Set

let arr = [1, 1, 2, 3, 4, 5, 5, 6]
let arr2 = [...new Set(arr)]

reduce()

let arr = [1, 1, 2, 3, 4, 5, 5, 6]
let arr2 = arr.reduce(function (ar, cur) {
  if (!arr.includes(cur)) {
    arr.push(cur)
  }

  return arr
}, [])

filter()

// 这种方法会有一个问题:[1,'1']会被当做相同元素,最终输入[1]
let arr = [1, 1, 2, 3, 4, 5, 5, 6]
let arr2 = arr.filter(function (item, index) {
  // indexOf() 方法可返回某个指定的 字符串值 在字符串中首次出现的位置
  return arr.indexOf(item) === index
})

7.DOM 事件有哪些阶段?谈谈对事件代理的理解

分为三大阶段:捕获阶段--目标阶段--冒泡阶段

事件代理简单说就是:事件不直接绑定到某元素上,而是绑定到该元素的父元素上,进行触发事件操作时(例如'click'),再通过条件判断,执行事件触发后的语句(例如'alert(e.target.innerHTML)')

好处:(1)使代码更简洁;(2)节省内存开销

8.什么是跨域?跨域请求资源的解决方法有哪些?

由于浏览器同源策略,凡是发送请求 url 的协议、域名、端口三者之间任意一与当前页面地址不同即为跨域。存在跨域的情况:

  • 网络协议不同,如 http 协议访问 https 协议。

  • 端口不同,如 80 端口访问 8080 端口。

  • 域名不同,如 qianduanblog.com 访问 baidu.com。

  • 子域名不同,如 abc.qianduanblog.com 访问 def.qianduanblog.com。

  • 域名和域名对应 ip,如 www.a.com 访问 20.205.28.90.

解决办法

  • porxy 代理

  • CORS

  • jsonp 缺点:这种方式无法发送 post 请求(这里),另外要确定 jsonp 的请求是否失败并不容易,大多数框架的实现都是结合超时时间来判定。

9.谈谈垃圾回收机制方式及内存管理

定义和用法:垃圾回收机制(GC:Garbage Collection),执行环境负责管理代码执行过程中使用的内存。

原理:垃圾收集器会定期(周期性)找出那些不在继续使用的变量,然后释放其内存。但是这个过程不是实时的,因为其开销比较大,所以垃圾回收器会按照固定的时间间隔周期性的执行。

垃圾回收策略:标记清除(较为常用)和引用计数。

  • 标记清除:

    定义和用法:当变量进入环境时,将变量标记"进入环境",当变量离开环境时,标记为:"离开环境"。某一个时刻,垃圾回收器会过滤掉环境中的变量,以及被环境变量引用的变量,剩下的就是被视为准备回收的变量。

    到目前为止,IE、Firefox、Opera、Chrome、Safari 的 js 实现使用的都是标记清除的垃圾回收策略或类似的策略,只不过垃圾收集的时间间隔互不相同。

  • 引用计数:

    定义和用法:引用计数是跟踪记录每个值被引用的次数。

    基本原理:就是变量的引用次数,被引用一次则加 1,当这个引用计数为 0 时,被视为准备回收的对象。

10.内存泄漏

  • 内存泄漏是指计算机可用的内存越来越少,主要是因为程序不能释放那些不再使用的内存。

  • 无意的全局变量、循环、定时器、回调会触发内存泄漏

11.undefined 和 null 有什么区别

  • null 表示"没有对象",即该处不应该有值

  • undefined 表示"缺少值",就是此处应该有一个值,但是还没有定义

12.介绍一下 JavaScript 原型,原型链,它们有何特点

JavaScript 中所有的对象都是由它的原型对象继承而来。而原型对象自身也是一个对象,它也有自己的原型对象,这样层层上溯,就形成了一个类似链表的结构,这就是原型链,所有原型链的终点都是 Object 函数的 prototype 属性。Objec.prototype 指向的原型对象同样拥有原型,不过它的原型是 null,而 null 则没有原型

13.avaScript 如何实现继承

  • 原型链继承
function Animal() {}
Animal.prototype.name = 'cat'
Animal.prototype.age = 1
Animal.prototype.say = function () {
  console.log('hello')
}

var cat = new Animal()

cat.name // cat
cat.age // 1
cat.say() // hello
  • 构造继承
function Animal() {
  this.species = '动物'
}
function Cat(name, age) {
  Animal.call(this)
  this.name = name
  this.age = age
}

var cat = new Cat('豆豆', 2)

cat.name // 豆豆
cat.age // 2
cat.species // 动物
  • 组合继承
function Animal() {
  this.species = '动物'
}

function Cat(name) {
  Animal.call(this)
  this.name = name
}

Cat.prototype = new Animal() // 重写原型
Cat.prototype.constructor = Cat
  • extends 继承 ES6 新增继承方式,Class 可以通过 extends 关键字实现继承
class Animal {}

class Cat extends Animal {
  constructor() {
    super()
  }
}

14.new 操作符具体干了什么

  • 创建一个空对象,并且 this 变量引用该对象,同时还继承了该函数的原型

  • 属性和方法被加入到 this 引用的对象中

  • 新创建的对象由 this 所引用,并且最后隐式的返回 this

15.冒泡排序

第一种

var arr = [89, 12, 8, 14, 23, 45, 9, 15, 33]
function bubble_sort(arr) {
  var len = arr.length
  if (arr.length == 1) {
    return
  }
  for (var i = 0; i < len; i++) {
    for (var j = 0; j < len - 1; j++) {
      //如果前一个值比后一个值大,那么交换位置
      if (arr[j] > arr[j + 1]) {
        var temp = arr[j]
        arr[j] = arr[j + 1]
        arr[j + 1] = temp
      }
    }
  }
  return arr
}
//测试一下代码
bubble_sort(arr)
//log [8, 9, 12, 14, 15, 23, 33, 45, 89]

第二种

function quickSort(arr) {
  //如果数组<=1,则直接返回
  if (arr.length <= 1) {
    return arr
  }
  var pivotIndex = Math.floor(arr.length / 2)
  //找基准,并把基准从原数组删除
  var pivot = arr.splice(pivotIndex, 1)[0]
  //定义左右数组
  var left = []
  var right = []

  //比基准小的放在left,比基准大的放在right
  for (var i = 0; i < arr.length; i++) {
    if (arr[i] <= pivot) {
      left.push(arr[i])
    } else {
      right.push(arr[i])
    }
  }
  //递归
  return quickSort(left).concat([pivot], quickSort(right))
}

第三种

function selectionSort(array) {
  var length = array.length,
    i,
    j,
    minIndex,
    minValue,
    temp
  for (i = 0; i < length - 1; i++) {
    minIndex = i
    minValue = array[minIndex]
    for (j = i + 1; j < length; j++) {
      if (array[j] < minValue) {
        minIndex = j
        minValue = array[minIndex]
      }
    }
    // 交换位置
    temp = array[i]
    array[i] = minValue
    array[minIndex] = temp
  }
  return array
}

16.javascript 是弱类型语言吗

JavaScript 一种直译式脚本语言,是一种动态类型、弱类型、基于原型的语言。它的解释器被称为 JavaScript 引擎,为浏览器的一部分,广泛用于客户端的脚本语言,最早是在 HTML 网页上使用,用来给 HTML 网页增加动态功能。

强类型定义语言:

  • 强制数据类型定义的语言。也就是说,一旦一个变量被指定了某个数据类型,如果不经过强制转换,那么它就永远是这个数据类型了。举个例子:如果你定义了一个整型变量 a,那么程序根本不可能将 a 当作字符串类型处理。强类型定义语言是类型安全的语言。

弱类型定义语言:

  • 数据类型可以被忽略的语言。它与强类型定义语言相反, 一个变量可以赋不同数据类型的值。

  • 强类型定义语言在速度上可能略逊色于弱类型定义语言,但是强类型定义语言带来的严谨性能够有效的避免许多错误。

17.箭头函数与普通函数的区别

  • 箭头函数没有自己的 this,继承的是外层代码块的 this。

  • 不可以当做构造函数,也就是说不可以使用 new 命令,否则会报错的。

  • 不可以使用 arguments 对象,该对象在函数体内不存在。如果要用,可以用 rest 参数代替。

  • 不可以使用 yield 命令,因此箭头函数不能用作 Generator(生成器) 函数。

  • 因为没有 this,所以不能使用 call、bind、apply 来改变 this 的指向。

18.call,apply 和 bind 的区别

它们在功能上是没有区别的百,都是改变 this 的指向,它们的区别主要是在于方法的实现形式和参数传递上的不同。call 和 apply 方法都是在调用之后立即执行的。而 bind 调用之后是返回原函数,需要再调用一次才度行

  • 写法的区别:

函数.call(对象问,arg1,arg2....)

函数.apply(对象,[arg1,arg2,...])

var ss=函数.bind(对象,arg1,arg2,....)

19.简单说一下你对 http 和 https 的理解

  • http 是一种超文本传输协议,传输的数据都是未加密的,也就是显示在明面上的,是现在互联网上应用最为广泛的一种网络协议,相对来说不太安全,但是所需成本很小。http 一般的端口号为 80.

  • https 则是具有安全性的 ssl 加密传输协议。简单来说,https 是一种安全版的 http,传输的数据是通过 SSL 加密的,比起 http 来说很安全,https 协议的主要作用就是:建立一个信息安全通道,来确保数组的传输,确保网站的真实性。但是相对来说,成本所需较高,尤其是所需 ca 证书等级越高,费用越高(ca 证书功能越强大,所需费用越高)。https 一般的端口号为 443。

  • http 和 https 各有各自的优缺点,http 花费少,安全性不高;https 花费稍高,但是更安全;

20.同步和异步

  • 同步,是所有的操作都做完,才返回给用户结果。即写完数据库之后,在相应用户,用户体验不好。

  • 异步,不用等所有操作等做完,就相应用户请求。即先相应用户请求,然后慢慢去写数据库,用户体验较好。

21.讲讲js数据类型?

  • 基本类型: Sting Number Boolean null undefined Symbol bigint

  • 引用类型: Object Date function RegExp Array

22.说说你对函数式编程的理解?

函数式编程是一种编程范式,可以理解为是利用函数把运算过程封装起来,通过组合各种函数来计算结果。函数式编程与命令式编程最大的不同其实在于,函数式编程关心数据的映射,命令式编程关心解决问题的步骤。

23.函数柯里化的理解?

是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数而且返回结果的新函数的技术。

24.为什么需要柯里化?

  • 把多个参数转化为单参数函数的级联,达到了动态确定参数的目的。

  • 当某些参数不确定时,可以先保留一个存根。剩余的参数确定以后,就可以通过存根调用剩下的参数。

25.TCP 的三次握手和四次挥手

  • 三次握手

1.客户端主动发起 SYN

2.服务端收到并返回 SYN 以及 ACK 客户端的 SYN

3.客户端收到服务端的 SYN 和 ACK 后,发送 ACK 的 ACK 给服务端,服务端收到后连接建立

Client -> SYN -> Server

Server -> SYN/ACK -> Client

Client -> ACK -> Server

  • 四次挥手

1.客户端发送 FIN 给服务端

2.服务端收到后发送 ACK 给客户端

3.服务端发送 FIN 给客户端

4.客户端收到后,发送 ACK 的 ACK 给服务端,服务端关闭,客户端等待 2MSL 后关闭

Client -> FIN -> Server

Server -> ACK -> Client

Server -> FIN -> Client

Client -> ACK -> Server -> CLOSED

Client -> 2MSL 的时间 -> CLOSED

26.HTTP 的缓存策略知道吗?

  • 强缓存

不会向服务器发送请求,直接从缓存中读取资源,在chrome控制台的Network选项中可以看到该请求返回200的状态码,并且Size显示from disk cache或from memory cache。强缓存可以通过设置两种 HTTP Header 实现:Expires 和 Cache-Control。

  • 协商缓存

协商缓存就是强制缓存失效后,浏览器携带缓存标识向服务器发起请求,由服务器根据缓存标识决定是否使用缓存的过程,主要有以下两种情况: 1. 协商缓存生效,返回304和Not Modified 2. 协商缓存失效,返回200和请求结果

27.从URL输入到页面展现到底发生什么?

DNS 解析:将域名解析成 IP 地址

TCP 连接:TCP 三次握手

发送 HTTP 请求

服务器处理请求并返回 HTTP 报文

浏览器解析渲染页面

断开连接:TCP 四次挥手

28.浏览器的渲染机制

解析HTML,生成DOM树,解析CSS,生成CSSOM树

将DOM树和CSSOM树结合,生成渲染树(Render Tree)

Layout(回流):根据生成的渲染树,进行回流(Layout),得到节点的几何信息(位置,大小)

Painting(重绘):根据渲染树以及回流得到的几何信息,得到节点的绝对像素

Display:将像素发送给GPU,展示在页面上。

29.九种跨域方式实现原理

JSONP原理 利用 script 标签没有跨域限制的漏洞,网页可以得到从其他来源动态产生的 JSON 数据。JSONP请求一定需要对方的服务器做支持才可以。

CORS 需要浏览器和后端同时支持。IE 8 和 9 需要通过 XDomainRequest 来实现。 服务端设置 Access-Control-Allow-Origin 就可以开启 CORS)

postMessage 允许来自不同源的脚本采用异步方式进行有限的通信,可以实现跨文本档、多窗口、跨域消息传递。

Websocket Websocket是HTML5的一个持久化的协议,它实现了浏览器与服务器的全双工通信,同时也是跨域的一种解决方案。

Node中间件代理(两次跨域) 实现原理:同源策略是浏览器需要遵循的标准,而如果是服务器向服务器请求就无需遵循同源策略。

nginx反向代理 实现原理类似于Node中间件代理,需要你搭建一个中转nginx服务器,用于转发请求。

window.name + iframe window.name属性的独特之处:name值在不同的页面(甚至不同域名)加载后依旧存在,并且可以支持非常长的 name 值(2MB)。

location.hash + iframe 实现原理: a.html欲与c.html跨域相互通信,通过中间页b.html来实现。 三个页面,不同域之间利用iframe的location.hash传值,相同域之间直接js访问来通信。