JavaScript基础面试题

174 阅读1分钟

个人总结的一些JavaScript基础面试题

JavaScript 数据类型

基本类型:String、Number、Blooean、null、undefined、Symbol(ES6新增)、Bigint(ES2020新增)

引用类型:Object

ES6语法知道哪些? 分别怎么用?

声明命令的有 let 和 const , 箭头函数 , 扩展运算符 和 剩余运算符(...) , 模板字符串 , promise 、class

var , let , const 区别

let (定义变量) 、 const(定义常量) 有块级作用域 ,只能在块级作用域访问。而 var没有,可以跨块访问,不能跨函数访问。 然后 var 可以重复声明。

箭头函数的this指向

箭头函数没有this, 父级的this指向谁,箭头函数里的this就指向谁

模板字符串

用一对反引号 ( ` ) 标识,它可以当作普通字符串使用, 如果是表达式的话 需要用 ${} 来包裹。

promise 用法

promise是构造函数, 是用来回调地狱的

promise构造函数本身是同步执行的,而 then 方法是异步指向的(then的返回值是新的promise实例,能够保证链式调用)

promise 有三种状态, pengding 进行中,resolved 已完成,rejected 已失败

promise.all 和 promise.race 分别怎么用?

promise all 方法是 并行执行异步操作, race和它功能一样, 只不过区别在于: all 是谁执行的慢,就返回谁, race和他相反,谁执行的快就返回谁。

async await

async 是英文“异步”的简写, await 可以认为是 async wait 的简写。明白俩个单词就很好理解, async 是用来 声明一个 function 是异步的, 然后 await 就是用于等待一个异步方法执行的。

async 函数:

async返回值: async函数中默认返回值是一个 Promise 对象, return 一个直接量的话, 那么这个直接量会通过 Promise.reslove() 封装成 Promise 对象。

  • asyncFunction的返回值是 promise 对象
  • promise对象的结果是由 asyncFunction 执行的返回值决定

await 表达式:

  • await 右侧的表达式一般为promise对象,但也可以是其他的值
  • 如果表达式是promise对象,await返回的是promise成功的值
  • 如果表达式是其他值,直接将此值作为await的返回值。

如何捕获异常

用 try 和 catch 捕获异常

async function fn(){
    try{
        let a = await Promise.reject('error')
    }catch(error){
        console.log(error)
    }
}

注意:

  • await必须写在async函数中,但async函数中可以没有await
  • 如果await的promise失败了,就会抛出异常,需要通过 try...catch来进行捕获。

宏任务和微任务

  1. JS中用来存储执行回调函数的队列包含2个不同待定的队列
  2. 宏队列:用来保存执行的宏任务(回调),比如:定时器回调/DOM 事件回调 / ajax回调
  3. 微队列: 用来保存执行的微任务(回调),比如: promise的回调 / MutationObserver 的回调
  4. JS执行时会区别这俩个队列
    • JS引擎首先必须先执行所有的初始化同步任务代码

    • 每次准备取出第一个宏任务执行前,都要将所有的微任务一个一个取出来执行。

手写函数防抖和节流

函数防抖

把频繁触发的动作放在最后执行一次。

const btn = document.querySelector('#btn')
let timer = null
btn.onclick =function(){
  if(timer){
    console.log('取消');
    window.clearTimeout(timer)
  }
   timer = setTimeout(()=>{
    console.log('打印');
    timer = null
   },1000)
}

函数节流

我一般把 节流 理解为 游戏中的技能冷却时间

const btn = document.querySelector('#btn')
let cd = false
function fn(){
  console.log('释放技能');
}
btn.onclick = function(){
  if(cd){
    console.log('冷却中');
  }else{
    cd = true
    fn()
    setTimeout(()=>{
      cd = false
    },3000)
  }
}

手写AJAX

const request = new  XMLHttpRequest()
// 设置请求方式 和 url
request.open('get','/xxxx')
request.onreadystatechange = function(){
    // 判断 (服务端返回了所有结果, 也就是状态4时)
    if(request.readyState === 4){
        console.log('请求完成')
        // 判断 响应状态码 
        if(request.status >= 200 && request.status < 300 ){
            console.log('请求成功')
        }else{
            // 处理错误信息
        }
    }
}
request.send()

this 指向问题

  • 如果是一个函数, this 指向 window
  • 如果是一个对象, this 指向这个对象
  • 如果调用了 call apply bind , this 就指向调用的那个东西
  • 如果是一个构造函数, this 指向新的对象实例
  • 如果是箭头函数, this 指向 外面的this (这个外面根据以上几种类型判断this)

闭包/立即执行函数是什么

闭包: 就是函数内部访问外部的变量

立即执行函数:

  • 声明一个匿名函数
  • 马上调用这个匿名函数

立即执行函数的作用: 创建一个独立的作用域。这个作用域的变量,外面访问不到(即避免变量污染)

什么是跨域,什么是 JSONP,什么是 CORS?

什么是跨域

跨域是指从一个域名的网络去请求另一个域名的资源

跨域问题的出现主要是因为浏览器的同源策略, 即浏览器必须保证只有 协议 + 端口 + 域名 才能 实现ajax请求

只要 协议,域名,端口有任何一个的不同,就被当作是跨域。

什么是JSONP?

JSONP(JSONP with Padding) 是一大堆牛逼的程序员想出来的跨域方法。

当前网站创造一个script标签去请求另一个网站的JS,然后这个JS文件会夹带一些数据,这些数据会在我的网站上调用全局函数运行。

JSONP是怎么实现的?

因为我们当前浏览器或者某些条件不支持CORS来实现跨域,所以我们必须要使用另外一种方式来跨域,于是我们就请求JS文件,这个JS文件会执行一个回调,这个回调里面就有我们的数据。

这个回调名字是可以随机生成的,我们把这个随机数以callback为参数传给后台,后台会把这个函数再次返回给我们并执行

JSONP的优点和缺点

优点:

  • 支持IE
  • 可以跨域(好像是废话哦。。。)

缺点:

  • 由于它是script标签,它不能像AJAX一样,读出(拿到)状态码和响应头,它只知道成功和失败(用onload和onerror来监听)。
  • 由于它是script标签,它只能发get请求,不支持POST。

JSONP的原理

  • 网页有一些标签天生具有跨域的能力,比如:img link iframe script
  • JSONP 就是利用 script标签的跨域能力来发送请求的(这也是jsonp跨域只能用get请求的原因所在

CORS : (Cross-Origin Resource Sharing) 跨域资源共享 。

它是由一个系列传输的 HTTP 头组成, 这些 HTTP 头决定浏览器是否阻止前端JavaScript 代码获取跨域请求的响应.

浅拷贝与深拷贝

浅拷贝

  • 赋值运算符 = 实现的是浅拷贝,只拷贝对象的引用值;
  • JavaScript 中数组和对象自带的拷贝方法都是“首层浅拷贝”;
    • 数组的 concat 和 slice
    • Object.assgn 和 ... 扩展运算符

深拷贝

  • 利用 JSON 对象中的 parse 和 stringify
    • 有缺点,有个对象中有函数,则会该函数会消失(原因undefined、function、symbol 会在转换过程中被忽略)
  • 利用递归来实现每一层都重新创建对象并赋值

使用正则实现 trim()

function trim(string){
	return string.replace(/^\s+|\s+$/g,'')
}

不使用 class 如何实现继承? 用 class 又如何实现?

不使用 class 如何实现继承

function Animal(color){
  this.color = color
}
function Dog(color,name){
  Animal.call(this,color)
  this.name = name
}

function Temp(){}
Temp.prototype = Animal.prototype
Dog.prototype = new Temp()
Dog.prototype.constuctor = Dog
Dog.prototype.say = function(){console.log('汪汪');}

let dog = new Dog('黄色','阿黄')

用 class 又如何实现

 class Animal{
     constructor(color){
         this.color = color
     }
     move(){}
 }
 class Dog extends Animal{
     constructor(color, name){
         super(color)
         this.name = name
     }
     say(){}
 }

如何实现数组去重

借鉴计数排序的原理,使用hash

unique = (array) => {
    const hash = []
    for(let i=0;i<array.length; i++){
        hash[array[i]] = true
    }
    const result = []
    for(let k in hash){
        result.push(k)
    }
    return result
}

缺点:只支持数字或者字符串数组,如果数组里面有对象,比如 array = [{number:1}, 2],就会出错。

使用 Set

unique = (array) => {
    return [...new Set(array)] 
    // 或者 return Array.from(new Set(array))
}

缺点:不支持对象去重

Map数据结构实现

支出所有类型去重

unique = (array) => {
  let map = new Map();
  let result = []
  for (let i = 0; i < array.length; i++) {
    if(map.has(array[i])) { // 判断 map 中是否已有该 key
      continue
    } else {  // 如果 map 中没有该 key,就加入 result 中
      map.set(array[i], true);
      result.push(array[i]);
    }
  }
  return result;
}