js面试(二)

  • 时间:
  • 浏览:
  • 来源:互联网

作用域

  1. 什么是作用域

    用来规定代码作用的范围及变量查询的范围

  2. 作用域的作用

    隔离变量, 防止命名冲突

  3. 作用域什么时候产生及销毁

    • 代码定义的时候产生

    • 函数执行完销毁的是变量对象而不是作用域

    • 作用域从代码定义的时候就一直在,除非没有当前代码

  4. 作用域链

    • 查找变量的时候现在当前作用域的变量对象中查找,如果有就使用,如果没有会继续去上级作用域查找,直到找到全局作用域,如果还没有就报错,报错内容: xxx is not defined, 查找的过程就是沿着作用域链查找
    • 本质: 是一个数组,数组包含上级所有的变量对象

执行上下文环境

js代码在正式执行之前js引擎会先做一些准备工作

  1. 创建执行上下文环境
  2. 创建一个空的对象(执行上下文对象),该对象用于收集当前作用域的:变量,函数,函数的参数
  3. 确认this的指向
  4. 创建当前环境的作用域链

闭包

  • 闭包的形成条件

    1. 函数嵌套

    2. 内部函数引用外部函数的局部变量

    3. 调用外部函数, 执行内部函数定义

      注意: 如果外部函数执行完后, 内部函数对象如果没有被外部变量引用, 内部函数对象与闭包就会立即被回收释放

  • 什么是闭包

    1. 闭包是抽象的概念
    2. 闭包其实指的就是满足一定的条件下(闭包的形成条件),会形成一个闭包的容器(非js对象)
    3. 该对象用于收集内部函数使用的外部函数的变量
    4. 该对象在内部函数调用的时候供内部函数使用
    5. 因为闭包形成的闭合对象保存在内部函数的[[scopes]]中

原型

  • 什么是原型对象

    1. 每个函数的prototype指向的是其显示原型对象

    2. 每个实例的__proto__指向的是其隐式原型对象

    3. 实例的隐式原型对象 === 其构造函数的显示原型对象

  • 原型链

    1. 对象查找属性的时候现在自身查找,如果自身没有沿着__proto__这条链查找
    2. 如果有就使用,如果没有就继续向上查找直到找到Object.prototype,如果还没有就返回undefined
  • 原型继承

    1. 子类的原型成为父类的实例
    2. 需要执行子类的构造属性constructor
  • 借用构造函数继承

    不是真正意义上的继承,只是简化子类构造函数的代码

  • 组合继承

    原型继承 + 借用构造函数继承

Promise的理解

  • ES6推出的新的更好的异步编程解决方案(相对于纯回调的方式)
    • 可以异步操作启动后或完成后, 再指定回调函数得到异步结果数据
    • 解决嵌套回调的回调地狱问题 —promise链式调用
  • promise对象有3种状态
    • pending
    • resolved/fulfilled
    • rejected
  • promise状态的2种变化
    • pending --> resolved
    • pending --> rejected
    • 注意: 变化是不可逆

promise的then()的理解

  • then()总是返回一个新的promise
  • 新promise的结果状态由then指定的回调函数执行的结果决定
    • 抛出错误
    • 返回失败的promise
    • 返回成功的promise
    • 返回其它任何值

Promise.all()

  • 语法: Promise.all([promise1, promise2, promise3])
  • 批量/一次性发送多个异步请求
  • 当都成功时, 返回的promise才成功
  • 一旦有一个失败的, 返回的promise就失败了

async/await与promise的关系

  • async/await是消灭异步回调的终极武器
  • 作用: 简化promise对象的使用, 不用再使用then/catch来指定回调函数
  • 但和Promise并不互斥
  • 反而, 两者相辅相成
  • 执行async函数, 返回promise对象
  • await相当于promise的then
  • try…catch可捕获异常, 相当于promise的catch

说说在项目中使用的ES6语法?

  • 新的语法:
    • const / let
    • 解构赋值
    • 模板字符串
    • 箭头函数 / 形参默认值 / rest参数
    • 扩展运算符(…)拆解数组或对象
    • 模板化相关语法
  • 新的API
    • Array.from() / Array.of()
    • arr.find() / arr.flat()
    • string.includes()
    • promise ==> async & await ==> 异步封装 / ajax请求
    • Map, Set ===> 解决深拷贝循环引用的问题, 数组去重
    • Proxy与Reflect ===> Vue3内部用来实现数据响应式

window.onload和$(document).ready()区别

  • window.onload是在待网页中所有内容加载完毕之后(包括图片)回调
  • 而$(documetn).ready()内部使用的是DOMContentLoaded监听, 在文档内容加载完成, 但图片还未加载加完前回调

区别 localStoarge与sessionStorage

  • 相同点:
    • 纯浏览器端存储, 大小不受限制, 请求时不会自动携带
    • 只能保存文本, 如果是对象或数组, 需要转换为JSON
    • API相同:
      • setItem(key, value)
      • getItem(key, value)
      • removeitem(key, value)
    • 浏览器不能禁用
  • 不同点:
    • localStorage保存在本地文件中, 除非编码或手动删除, 否则一直存在
    • sessonStorage数据保存在当前会话内存中, 关闭浏览器则清除

区别ajax请求与一般HTTP请求

  • ajax请求是一种特别的http请求
  • 对服务器端来说, 没有任何区别, 区别在浏览器端
  • 浏览器端发请求: 只有XHR或fetch发出的才是ajax请求, 其它所有的都是非ajax请求
  • 浏览器端接收到响应
    • 一般请求: 浏览器一般会直接显示响应体数据, 也就是我们常说的刷新/跳转页面
    • ajax请求: 浏览器不会对界面进行任何更新操作, 只是调用监视的回调函数并传入响应相关数据

post常用的数据格式,form-data和json的区别

  • application/json: json格式文本
  • application/x-www-form-urlencoded: 形如query参数(name=tom&age=12)的文本
  • multipart/form-data: 文件上传

手写代码(也可能让你说)

深拷贝

  • JSON.parse(JSON.stringfy(obj))
    • ===> 问题: 方法/函数会丢失
    • ===> 问题2: 循环引用会出错(死循环)
  • 递归遍历
    • 如果是基本类型与函数直接返回, 函数就不会丢失也不会拷贝
    • 如果是对象/数组创建拷贝对象/数组
    • 问题: 循环引用会出错的问题(死循环)
  • 使用Map缓存拷贝对象
    • 如果发现一个对象已经产生拷贝对象, 直接返回这人拷贝对象
    • 使用Map存储 ==> key为源对象, value是拷贝产生的对象 (不能用对象来存储, 因为对象的key为字符串)
  • 数组和对象用不同的遍历方式
    • 如果是对象使用for…in遍历内部数据
    • 如果是数组使用for/forEach遍历内部数据
/* 
1). 大众乞丐版
    问题1: 函数属性会丢失   原因: json字符串数据是不存在函数, 函数属性就会丢失
    问题2: 循环引用会出错   原因: 转换为json字符串是会产生死循环查找, 报错
利用JSON转换成json字符串, 再解析回来
*/
deepClone1 (target) {
  if (target!==null && typeof target==='object' ) {
    return JSON.parse(JSON.stringify(target))
  } else {
    return target
  }
},
 /* 
2). **面试基础版本,搞懂这个就行**
    解决问题1: 函数属性还没丢失
    问题2: 循环引用会出错  没有解决: 它会同个对象或数据进行不断拷贝
*/
deepClone2 (target) {
  if (target!==null && typeof target==='object') { // 非函数的对象
    // 创建一个新的空容器
    const clone = Array.isArray(target) ? [] : {}

    // 遍历target中所有数据, 依次添加到新容器
    for (const key in target) {  // key是对象的属性名或数组的下标
      if (target.hasOwnProperty(key)) { // 如果是容器自身的才需要处理。去除原型链的属性
      //属性递归调用
        clone[key] = typeof target[key] === 'object' ? deepCopy2(obj[key]) : target[key];
      }
    }
    return clone
  } else {
    return target
  }
},
/* 
3). 面试加强版本
  解决问题2: 循环引用正常

  缓存的容器
    问题1: 什么结构的?  Map, key为target, value是对应的拷贝对象
    问题2: 保存什么数据  key是target, value是target对应的拷贝对象
  注意: 在整个递归调用过程中, 只有一个map在反复使用
*/

deepClone3 (target, map=new Map()) {

  if (target!==null && typeof target==='object') { // 非函数的对象
   // const map = new Map()  // 用来缓存target与其对应的拷贝对象的容器   // 不能在这里
    // 从缓存中取出对应的拷贝对象,如果有了, 直接返回它
    let clone = map.get(target)
    if (clone) return clone

    // 如果没有, 创建一个新拷贝空容器, 缓存起来
    clone = Array.isArray(target) ? [] : {}
    map.set(target, clone)

    // 遍历target中所有数据, 依次添加到新容器
    for (const key in target) {  // key是对象的属性名或数组的下标
      if (target.hasOwnProperty(key)) { // 如果是容器自身的才需要处理
        clone[key] = aUtils.deepClone3(target[key], map) // 对属性值进行克隆处理后保存
      }
    }
    return clone
  } else {
    return target
  }
},
/* 
4). 面试加强版本2(优化遍历性能)
    数组: while | for | forEach() 优于 for-in | keys()&forEach() 
    对象: for-in 与 keys()&forEach() 差不多
*/
deepClone4 (target, map=new Map()) {

  if (target!==null && typeof target==='object') { // 非函数的对象
   // const map = new Map()  // 用来缓存target与其对应的拷贝对象的容器   // 不能在这里
    // 从缓存中取出对应的拷贝对象,如果有了, 直接返回它
    let clone = map.get(target)
    if (clone) return clone

    // 如果没有, 创建一个新拷贝空容器, 缓存起来
    if (Array.isArray(target)) {
      clone = []
      map.set(target, clone)
      // 遍历target数组中所有元素, 依次添加到新容器
      target.forEach((item, index) => {
        // clone.push(aUtils.deepClone4(item, map))
        clone[index] = aUtils.deepClone4(item, map)
      })

    } else {
      clone = {}
      map.set(target, clone)
      // 遍历target对象中所有数据, 依次添加到新容器
      for (const key in target) {  // key是对象的属性名或数组的下标
        if (target.hasOwnProperty(key)) { // 如果是容器自身的才需要处理
          clone[key] = aUtils.deepClone4(target[key], map) // 对属性值进行克隆处理后保存
        }
      }
    }

    return clone
  } else {
    return target
  }
},

数组相关

数组扁平化

  1. 使用数组原型中的falt方法

    let arr = [1, 2, 3, [4, 5, 6, [7, 8, [9, 10, 11]]]];
    arr.flat(Infinity);
    

数组去重

  1. 双重循环
//创建一个数组
      var arr = [1, 2, 3, 2, 2, 1, 3, 4, 2, 5];

      //去除数组中重复的数字
      //获取数组中的每一个元素
      for (var i = 0; i < arr.length; i++) {
        //console.log(arr[i]);
        /*获取当前元素后的所有元素*/
        for (var j = i + 1; j < arr.length; j++) {
          //console.log("---->"+arr[j]);
          //判断两个元素的值是否相等
          if (arr[i] == arr[j]) {
            //如果相等则证明出现了重复的元素,则删除j对应的元素
            arr.splice(j, 1);
            //当删除了当前j所在的元素以后,后边的元素会自动补位
            //此时将不会在比较这个元素,我需要在比较一次j所在位置的元素
            //使j自减
            j--;
          }
        }
      }
      console.log(arr);

  1. indexOf

    function removeRepeat(array) {
        var res = [];
        for (var i = 0, len = array.length; i < len; i++) {
            var current = array[i];
            if (res.indexOf(current) === -1) {
                res.push(current)
            }
        }
        return res;
    }
    
  2. filter

    function removeRepeat(array) {
        var res = array.filter(function(item, index){
            return array.indexOf(item) === index;
        })
        return res;
    }
    
    1. set方法
    function removeRepeat(array) {
       return [...new Set(array)];
    }
    

数组翻转

  1. 使用原型中的reverse方法

    let array = [1, 2, 3, 4, 5]
    array.reverse() 
    
  2. 循环

    for(var i = 0; i < arr.length; i++){
        var temp = arr[i];
        arr[i] = arr[arr.length - 1 - i]
        arr[arr.length - 1 - i] = temp;
    }
    

排序算法

  function bubbleSort(array) {
        // 1.获取数组的长度
        var length = array.length;

        // 2.反向循环, 因此次数越来越少
        for (var i = length - 1; i >= 0; i--) {
          // 3.根据i的次数, 比较循环到i位置
          for (var j = 0; j < i; j++) {
            // 4.如果j位置比j+1位置的数据大, 那么就交换
            if (array[j] > array[j + 1]) {
              // 交换
              const temp = array[j+1]
              array[j+1] = array[j]
              array[j] = temp
              // [array[j + 1], array[j]] = [array[j], array[j + 1]];
            }
          }
        }

        return array;
      }

本文链接http://www.dzjqx.cn/news/show-617000.html