前端面试小笔记

90 阅读36分钟

HTML+CSS

  1. 什么是BFC?如何触发?有何特点?如何解决margin塌陷
    • BFC,又称块级格式化上下文,是一个完全独立的空间,可以让空间中的子元素不会影响外边的布局
    • 解决了脱离文档流时,高度塌陷;margin边距重叠;两栏布局
  2. css如何出来溢出?说一下overflow不同值的区别
    • hidden、scroll、auto、visible
  3. 三栏布局有什么实现方式?
    • 浮动布局、绝对定位、flex布局、table布局、grid网格布局
  4. css calc属性作用是什么?主要用于解决什么问题?
    • 进行算法运算、单位换算、转换字体大小
    • 当我们处理css变量时;为了得到一个新的值;用于不同单元之间的计算;避免重复相同的操作计算
  5. 有一个固定的长宽div,怎么实现在屏幕上垂直水平居中
    • flex:justify-content,align-item;postion:absolute left: 50% top: 50% transform: translate(-50%,-50%);
  6. 描述一下渐进增强(progressive enhancement)和优雅增强(graceful degradation)
    • 渐进增强就是在一个基本功能完成的基础上,往高级方向进行发展兼容,为了就是得到更好的用户体验
    • 优雅增强就是在一个完整的功能上,逐步兼容低版本的浏览器
  7. iframe有哪些优点?缺点?用它来解决什么问题
    • 优点:模块分离,便于更改;可增加代码的 重用;减少重载整个页面
    • 缺点:会增加页面的请求次数以及http请求
  8. 描述下css盒子模型
    • 内到外:内容、内边距、边框、外边框
    • 分类:标准盒模型、怪异盒模型
  9. html5特性
    • 语义化标签、video、audio、canvas、新增本地存储、webworker、websocket
  10. css3特性
    • 选择器(:first-of-type)、新样式(border-radius、box-shadow、border-image)、背景属性(background-clip)、文字(word-wrap)、颜色 (rgba、hsla)、transtion、transform 转换、animation 动画、渐变、flex弹性布局、Grid栅格布局
  11. css中选择器的优先级
    • * 、div svg|a#id 、.cls、 [attr=value]、 :hover、 ::before
    • div.abc[class="123"]
  12. 重绘和回流
    • 重绘:是受外观变化而影响的
    • 回流:是受布局大小变化而重新排列生成
    • 回流必将引起重绘,重绘不一定会引起回流
  13. 浏览器的渲染流程
    • 解析 HTML Source,生成 DOM 树。
    • 解析 CSS,生成 CSSOM 树。
    • 将 DOM 树和 CSSOM 树结合,去除不可见元素(很重要),生成渲染树( Render Tree )。
    • Layout (布局):根据生成的渲染树,进行布局( Layout ),得到节点的几何信息(宽度、高度和位置等)。
    • Painting (重绘):根据渲染树以及回流得到的几何信息,将 Render Tree 的每个像素渲染到屏幕上。

javacript

  1. JavaScript的基本数据类型

    • string、number、null、undifined、Symbol、Bigint、Boolean
  2. JavaScript的引用数据类型

    • Object(对象)、Array(数组)、Function(函数)、Date(日期时间类型)、RegExp(正则表达式)
  3. 如何判断javascript的数据类型

    • typeof ,需要注意null检测为object

    • instanceof,主要用在引用类型的判断

      function myInstanceof(Fn, obj) {
        // 获取该函数显示原型
        const prototype = Fn.prototype;
        // 获取obj的隐式原型
        let proto = obj.__proto__;
        // 遍历原型链
        while (proto) {
          // 检测原型是否相等
          if (proto === prototype) {
            return true;
          }
          // 如果不等于则继续往深处查找
          proto = proto.__proto__;
        }
        return false;
      }
      
  4. 怎么判断两个对象相等?

  5. 如何判断空对象?

    • 通过JSON.stringify转化为字符串进行比较
    • for in 循环,
    • Object.keys()方法,返回对象的属性名组成的一个数组,若长度为0,则为空对象(ES6的写法)
    • Object.getOwnPropertyNames方法获取对象的属性名,存到数组中,若长度为0,则为空对象
  6. 0.1+0.2为什么不等于0.3

    • 0.1:0.0001 1001 1001 1001......
    • 0.2:0.0011 0011 0011 0011 ......
    • 0.1+0.2 不等于 0.3 ,因为在 0.1+0.2 的计算过程中发生了两次精度丢失。第一次是在 0.1 和 0.2 转成双精度二进制浮点数时,由于二进制浮点数的小数位只能存储52位,导致小数点后第53位的数要进行为1则进1为0则舍去的操作,从而造成一次精度丢失。第二次在 0.1 和 0.2 转成二进制浮点数后,二进制浮点数相加的过程中,小数位相加导致小数位多出了一位,又要让第53位的数进行为1则进1为0则舍去的操作,又造成一次精度丢失。最终导致 0.1+0.2 不等于0.3
  7. 强转类型转换、隐式类型转换分别是什么,列举场景说明

  8. 创建函数的几种方式

    • 函数声明式、函数表达式、函数对象等
  9. 列举宿主对象、内置对象、原生对象并说明其定义

    • 宿主对象:就是ECMASript实现的宿主环境提供的对象;如bom、dom
    • 原生对象(需要new关键字实例化)与内置对象(global、math)都是独立于宿主环境的、ECMASript实现提供的对象
  10. == 和 === 的区别?

    • == 只需要判断等号两边的值是否相等,不需要判断类型。值相同则返回true
    • === 既要判断两边的值是否相等,也要判断两边的类型是否相等。全等才能返回true
  11. null 、undefined的区别

    • null == undefinend 相等但不全等
    • 在与数值运算的时候,null为0,则undefined为NaN
  12. 什么情况下会返回undefined值?

    • 变量被声明但没有赋值的时候,就会出现undefined
    • 对象的某个属性没有复制时,该属性的值为undfined
    • 调用函数过程中,应该提供的参数没有提供时,该参数就等于undefined
    • 函数没有返回值时,默认返回undefined
  13. 如何区分数组和对象?

    • 通过Array.isArray方法来判断
    • 通过instanceof来比较
    • 数组上是有length属性的,而对象是没有的
  14. 多维数组如何降维?

    • 通过数组字符串化:array.split
  15. 如何获取当前的日期

  16. 什么是类数组,如何将其转化为真实的数组?

    • 类数组中会多出calles属性;只有部分object方法,不能调用Array的方法
    • slice、Array.from、扩展运算符
  17. 如何遍历对象的属性

    • 通过for in 遍历,但是缺点是Object.defineProperty的形式来设置对象的描述对象,遍历不出来,其次就是继承的属性会被遍历到
    • Object.key()、Object.getOwnPropertynames()、Object.entries()
  18. 如何给一个按钮绑定两个onclick事件

    • addEventListene()
  19. 变量提升是什么?与函数提升的区别?

    • 变量提升是将声明的变量提升到最顶端
    • 而函数提升是只针对具名函数进行提升,对于赋值的匿名函数不会
    • 区别:就是函数提升要优先于变量提升,且不会被同名变量声明覆盖,但是会被变量赋值后覆盖。而且存在同名函数与同名变量时,优先执行函数
  20. 什么是作用域链?如何延长?

    • 默认情况下,js 代码处于全局作用域(0级),当我们声明一个函数的时候,就会开辟一个局部作用域(1级)。 函数里面也可以声明函数,就会又形成局部作用域(2级),以此类推就会形成作用域链。
    • 执行环境的类型只有两种,全局和局部(函数),但是有些语句可以在作用域的前端临时增加一个变量对象,该变量对象会在代码执行后被移除。简而言之,执行以下两个语句时,作用域链都会得到加强
      1. try-catch 语句的catch 块;会创建一个新的变量对象,包含的是被抛出的错误对象的声明
      2. with 语句,会将指定的对象添加到作用域链中
  21. 如何实现数组的随机排序?

    • 利用sort+Math.random()
    • 创建一个新的数组,从原数组中随机抽取一个元素插入到新数组中,然后返回新数组
    • 数组内的元素随机替换位置(类似洗牌算法)
  22. dom节点的Attribute和Property有何区别?

    • HTML标签中定义的属性和值会保存该DOM对象的attributes属性里面
    • DOM有其默认的基本属性,而这些属性就是所谓的**“property”**,无论如何,它们都会在初始化的时候再DOM对象上创建。
    • property能够从attribute中得到同步;
    • attribute不会同步property上的值;
    • attribute和property之间的数据绑定是单向的,attribute->property;
    • 更改property和attribute上的任意值,都会将更新反映到HTML页面中;
  23. dom结构操作怎样添加、移除、移动、复制、创建和查找节点?

    • createDocumentFragment() 创建DOM片段
    • createElement() 创建具体元素
    • createTextNode() 创建一个文本节点
    • appendChild() 添加
    • removeChild() 移除
    • replaceChild() 替换
    • insertBefore() 插入
    • getElementsByTagName() 通过标签查找
    • getElementsByName () 通过元素name属性
    • getElementsByClassName () 通过类名查找
    • getElementsById() 通过Id查找
  24. 什么是事件冒泡,它是如何工作的?如何阻止事件冒泡?

    • 当一个元素接收到事件的时候 会把他接收到的事件传给自己的父级,一直到window
    • 它是通过有内到外,将事件按顺序触发
    • e.stopPropagation() || return false;
  25. 什么是事件捕获,它是如何工作?

    • 与事件冒泡相反,是由外到内,
  26. 如何让事件先冒泡后捕获?

    • 使用事件委托(Event Delegation):将单个事件监听器添加到父元素上,以处理其子元素上的事件。由于事件冒泡会在整个文档中传播,因此在父元素上添加事件监听器可以确保事件先冒泡后获取。
    • 使用 setTimeout():将事件处理程序延迟一小段时间再执行,以确保事件有时间传播到父元素。由于事件冒泡是在异步方式下完成的,因此使用 setTimeout() 函数可以确保事件先冒泡后获取。
  27. JavaScript动画和css3动画有什么区别?

    • JavaScript动画是运行在主线程中的,有可能造成阻塞,相对复杂的,同时它动画控制能力强以及效果丰富,兼容性少
    • 而css3动画,会在重绘回流中完成;提高动画性能;对动画本身控制能力弱;实现复杂动画,代码过于冗余
  28. dom的事件模型

    • 事件流:事件捕获阶段->事件目标阶段->事件冒泡阶段
  29. 事件三要素是什么?

    • 事件源:触发(被)事件的元素
    • 事件类型:事件的触发方式(例如鼠标点击或键盘点击)
    • 事件处理程序:事件触发后要执行的代码(函数形式)
  30. 获取元素的位置?

    • 获取可视区域的大小:clientheight、clientWidth
    • 获取目标可视区域在显示器上的坐标位置:offsetTop、offsetLeft
    • scrollTop(纵向滚动的距离),scrollLeft(横向滚动的距离)、scrollWidth(内容宽度)、scrollHeight(内容高度)
  31. 如何绑定事件?如何解除事件?

    • 绑定:行内进行绑定;通过元素.on事件=function(){};通过事件监听函数addEventListener
    • 解除:元素.on事件= null;对象.removEventListener()
  32. 对事件委托的理解?

    • 给父元素注册事件,委托子元素处理
    • 减少对事件的注册,提高的程序的性能
    • 1.给目标元素的父元素或者父元素之上的元素绑定事件。
    • 2.在父元素或者父元素以上的元素注册的事件 通过事件对象.targe来获取当前点击的那个元素。
    • 3.交给子元素去处理。
  33. setTimeout和setInterval的区别及用法是什么?

    • setTimeout是通过一定时间后执行一次;参数两个(函数体,时间)
    • setInterval是通过一定的间隔时间,重复执行;参数两个(函数体,时间)
    • 两种定时器的消除方法不同
  34. 用setTimeout来实现setInterval并实现清楚多个定时器?

    function myInterval(func, time) {
    
        let ids = [];
    
        function fn() {
    
            let id = setTimeout(() => {
    
                func();
    
                fn();
    
            }, time)
    
            ids.push(id);
    
        }
    
        fn();
    
        return ids;
    
    }
    
    let id = myInterval(() => {
    
        document.write('Hello World');
    
    }, 500)
    
    
    function clearMyInterval(idList) {
    
        idList.forEach((id) => {
    
            clearTimeout(id);
    
        })
    
    }
    
    setTimeout(() => {
    
        clearMyInterval(id)
    
    }, 3000)
    
  35. document.write和innerHTML区别?

    • docunment.write是会将整个页面进行重绘
    • 而innnerHTML只会将部分页面进行重绘
  36. 元素拖动如何实现,原理是怎样?

    • <body>
          <div id="rect-1" class="rect">
              <header>拖住我</header>
          </div>
      </body>
      <script>
          const box = document.querySelector(".rect"),
          header = box.querySelector("header")
          function move ({movementX,movementY}){
              let getStyle = window.getComputedStyle(box);
      
              let left = parseInt(getStyle.left);
              let top = parseInt(getStyle.top);
      
              box.style.left = `${left+movementX}px`
              box.style.top = `${top+movementY}px`
          };
          header.addEventListener('mousedown',()=>{
              header.addEventListener('mousemove',move);
          })
          document.addEventListener('mouseup',()=>{
              header.removeEventListener('mousemove',move);
          })
      </script>
      
    • 原理:监听目标元素被按下移动的鼠标距离,然后设置到目标元素的top和left上,再通过监听鼠标弹起,移除鼠标的移动事件

  37. 什么是重绘?什么 是回流?如何最小化重绘和回流?

    • 重绘是指浏览器根据元素的样式进行重新绘制
    • 回流是指页面布局发生变化,浏览器需要重新计算元素的位置和大小
    • 1.需要对元素进行复杂的操作时,可以先隐藏(display:'none'),操作完成后再现身
    • 2.需要创建多个DOM节点时,使用DocumentFragment创建完后一次性的加入document缓存Layout属性值,如:var left=elem.offsetLeft;这样,多次使用left只产生一次回流
    • 3.尽量避免使用table布局(table 元素一旦触发回流就会导致table里所有的其他元素回流)
    • 4.避免使用css表达式(expresssion),因为每次调用都会重新计算值(包括加载页面)
    • 5.尽量使用css属性简写,如:用border 代替 border-width,border-style,body-color
    • 6.批量修改元素样式,elem.className和elem.style.cssText代替elem.style.xxx
  38. 延迟加载的方式有哪些?

    • js延迟加载就是等页面加载完成之后在加载JavaScript文件,有助于提高网页的加载速度
    • 方式:
      1. defer属性:表明脚本在执行时,不会影响页面的构造,也就是脚本会被延迟到整个页面都解析完毕之后在执行
      2. async属性:不让页面等待脚本下载和执行,从而异步加载页面其他内容
      3. 动态创建DOM方式:将创建DOM的script脚本放置在标签前, 接近页面底部
      4. 使用setTimeout延迟方法:延迟加载js代码,给网页加载留出更多时间
      5. 让JS最后加载:把js外部引入的文件放到页面底部,来让js最后引入,从而加快页面加载速度
  39. 垃圾回收机制有哪些?具体怎么如何执行?

    • 垃圾回收算法就是垃圾收集器按照固定的时间间隔,周期性地寻找那些不再使用的变量,然后将其清除或释放内存
    • 如何清除:标记清除;引用计数
      1. 标记清除:首先遍历所有堆内存的所有对象,分别给他们打上标记,然后通过在代码执行过程结束后,对所使用过的变量取消标记。再清除阶段再把具有标记的内存对象进行整体清除,从而释放内存空间。最后通过标记整理算法(标记整理算法会将活着的对象,向一端移动,最后清理掉边界内存),清除掉内存碎片
      2. 引用计数:
  40. 什么是内存泄漏?

    • 内存泄露是指在js已经分配内存地址的对象由于长时间未进行内存释放或者无法清除,造成了长期占用内存,使得内存资源浪费,最终导致运行的应用响应速度变慢以及最终崩溃的情况
    • 造成内存泄漏的原因:
      1. 过多的缓存。及时清除缓存
      2. 滥用闭包。尽可能避免使用大量的闭包
      3. 定时器或回调太多。当不需要setTimeout或setInterval时,定时器没有被清除,定时器的糊掉函数以及其内部依赖的变量都不能被回收,会造成内存泄漏。解决方法:在定时器完成工作时,需要手动清除定时器
      4. 太多无效的DOM引用。DOM删除了,但是节点的引用还在,导致GC无法实现对其所占内存的回收。解决方法:给删除的DOM节点引用设置为null。
      5. DOM对象和JS对象相互引用
  41. 数组遍历的方法有哪些,分别有什么特点,性能如何?

    • for循环 (其中将arr.length的长度赋予到临时变量中),则会性能更优
    • forEach
    • for in
    • for of
    • map
    • every
    • some
    • reduce
    • filter
  42. ES5和ES6的区别,ES6新增了什么?

    • 新增
      1. let和const
      2. 展开运算符
      3. set对象
      4. Map对象
      5. 箭头函数
      6. 数组新增方法:array.isArray();Array.from();Array.of();arr.find();arr.findIndex();arr.includes();
      7. 字符串新增方法:str.startswith();str.endswith();str.repeat(times);
      8. 模板字符串
      9. babel编译器
      10. 对象简化方法,
      11. proxy
      12. 解构赋值,async,promise
  43. ES6的继承和ES5的继承有什么区别?

    • ES6的继承:在 ES6 中,两个类之间的继承就是通过 extends 和 super 两个关键字实现的。
    • ES5的继承:在 ES5 中,构造函数 B 的实例继承构造函数 A 的实例属性是通过 A.call(this) 来实现的
    • 第一个是,ES6 中子类会继承父类的属性,第二个区别是,super() 与 A.call(this) 是不同的,在继承原生构造函数的情况下,体现得很明显,ES6 中的子类实例可以继承原生构造函数实例的内部属性,而在 ES5 中做不到
  44. var、let、const之前那的区别?暂时性死区如何理解?

    • 暂时性死区:就是在没有定义的情况下使用该变量
    • var:存在变量提升,可以重复声明,不存在块级作用域
    • const:不存在变量提升,不可以重复声明,存在块级作用域
    • let:不存在变量提升,变量可以改变,
  45. Class、extends是什么,有什么作用?

    • class,在面向对象编程中,类是一种创建对象的蓝图和模板。它定义了对象的结构通过成员变量和行为通过方法。通过类你可以创建多个具有相同属性和行为的对象;同时还提供了封装,可以将数据和对这些数据的操作结合在一起。
    • Extends,继承是面向对象编程的核心概念,允许你基于另一个类创建一个类。新类继承父类的属性和方法,并可以添加新的属性和方法 或重写继承的方法;继承的主要目的是提供代码的重用,并建立一个自然的分类层次结构
  46. 什么是js闭包

    • 当通过调用外部函数返回的内部函数后,即使外部函数执行完成,但是内部函数引用外部函数的变量,就导致变量依然存放在内存中,我们把这些集合的变量,称为闭包
    • 优点:模块化开放(实现共有变量);做缓存;可以封装私有化属性;防止全局变量污染
    • 缺点:一旦形成闭包,只有在页面关闭后闭包占用的内存才会被回收,所以造成了内存泄漏
  47. 说一下类的创建和继承,列举一下你所知道的继承方式

    • 类的创建:new一个function,在function的prototype里面增加属性和方法

    • 类的继承:

      1. 原型继承:

        function Persion(name){
        	this.name = name;
        }
        Persion.prototype.id = 10
        function son (sex){
        	this.sex = sex;
        }
        //son就继承了persion的name属性
        son.prototype = new Persion('11')
        var b = new son()
        b.name//
        b.sex//
        b.id// 
        
      2. 类继承

        function Persion(name,age){
        	this.name = name;
        	this.age = age;
        }
        Persion.prototype.id = 10
        function Boy(name,age,sex){
        	Persion.call(this,name,age)
        	this.sex = sex
        }
        var b = new Boy('1','2','3')
        b.name//
        b.sex//
        b.id
        
      3. 混合继承

        function Persion(name,age){
        	this.name = name;
        	this.age = age;
        }
        Persion.prototype.id = 10
        function Boy(name,age,sex){
        	Persion.call(this,name,age)
        	this.sex = sex
        }
        Boy.prototype = new Persion();
        var b = new Boy('1','2','3')
        b.name//
        b.sex//
        b.id //
        
      4. 寄生组合继承

        function Persion(name,age){
        	this.name = name;
        	this.age = age;
        }
        Persion.prototype.id = 10
        function Boy(name,age,sex){
        	Persion.call(this,name,age)
        	this.sex = sex
        }
        (function(){
            //创建一个空对象
            var Super = function(){};
            //将实例作为子类的原型
            Super.prototype = Persion.prototype;
            Boy.prototype = new Super();
        })()
        
        var Boy = new Boy();
        
  48. 如何解决异步回调地狱

    • 回调地狱:异步回调函数的层层嵌套

    • Promise是解决异步编程的一种解决方案

      const p1 = new Promise((resolve,reject) =>{})
      const p2 = new Promise((resolve,reject) =>{})
      const p3 = new Promise((resolve,reject) =>{})
      
      p1.then((data) =>{
      	return p2
      }).then((data)=>{
      	return p3
      }).then((data)=>{
      	console.log(data)
      })
      
      1. promise有三种状态:pending、fulfilled、rejected
      2. pending 到fulfilled,执行resolve() ;pending 到rejected
      3. 本质是控制异步代码的结果处理的顺序
      4. Promise的使用:
        • 实例化Promise对象:将异步操作放入Promise中
        • 调用then()方法:处理异步操作结果
    • 异步函数async和await

      function getPromise(key){
          let p = new Promise((resolve,reject) => {
              //异步操作
              
          })
      	return p;
      }
      const a = async function (){
      	let data1 = await getPromise('1')
      	let data2 = await getPromise('2')
      	let data3 = await getPromise('3')
      }
      
      1. async和await异步函数 : 这两个关键字只能用于函数, 所以用的时候一定要放在函数里面用

        async关键字: 修饰函数, 表示这个函数内部有异步操作。

        await关键字: 等待异步执行完毕

      2. 使用方式:

        • 函数前面使用async修饰
        • 函数内部,promise操作使用await修饰
  49. 说一下图片的懒加载和预加载

    • 预加载:提前加载所需要的图片资源,加载完毕后会缓存到本地,当需要时,可以立马显示出来,以达到在预览的过程中,无需等待直接预览的良好体验
      1. 缺点是占用较多的后台资源
    • 懒加载:需要的时候再去加载资源
      1. 比较耗性能
      2. 懒加载主要是监听body或者其他存放图片且滚动的元素scroll事件,每次滚动检查图片是否显示
      3. 如何检测图片进入可视区域
        • 获取可视区高度:window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth
        • 当scrollTop+clientHeight >offsetTop 请添加图片描述img
  50. mouseover和mouseenter的区别

    • mouseover:当鼠标移入元素或其子元素都会被触发事件,所以有一个重复触发,冒泡的过程
    • mouseenter:当鼠标移除元素本身(不包含元素的子元素)会触发事件,也就是不会冒泡,
  51. new操作符做了哪些事情?

    • 新建了一个对象obj;

    • 把obj和构造函数通过原型链连接起来

    • 将构造函数的this指向obj

      function Person(name,age) {
          this.name = name;
          this.age = age;
      }
      Person.prototype.sayName = function () {
          console.log(this.name)
      }
      function myNew(func,...args){
          //创建一个新对象,使用现有的对象来提供新创建的对象的_proto_
          const obj = Object.create(func.prototype)
          let result = func.apply(obj,args)
          return result instanceof Object ? result : obj
      }
      let p = myNew(Person,"huihui",123)
      console.log(p) //
      p.sayName // 
      
  52. 改变函数内部this指针的指向函数(bind,apply,call区别),内在分别是如何实现的? 图片转存失败,建议将图片保存下来直接上传

    • call、apply、bind都是用来改变函数的this指向的

    • call和apply改变this的同时,也会把目标函数给执行掉;bind只负责改造this,不作任何执行操作

    • 唯独apply在传参的时候,可以以数组形式被传入

    • 实现call方法:

      Function.prototype.myCall = function(context,...args){
      	//把函数挂到目标对象上
          context.func = this
          //执行函数
          context.func(...args)
          //删除1中改到目标对象上的函数,把目标对象“完璧归赵”
          delete context.func
      }
      
      var me = {
          name :"icon"
      }
      function showFullName(surName) {
        console.log(`${this.name} ${surName}`)
      }
      showName.myCall(me, 'lee')
      
    • 模拟apply方法

      Function.prototype.myApply = function(context,args){
          //判断当前传参是否是数组
          if(args && !(args instanceof Array)){
              throw new TypeError('error')
          }
          //如果是null 默认指向window
          context = context || window
          //把函数挂到目标对象上
          context.func = this
          //执行函数并且存储上面说的返回值
          const result = context.func(args ?[...args] : "")
        	//删除1中挂到目标对象上的函数,
          delete context.func;
          //返回结果
          return result
      }
      
    • 模拟bind方法

      Function.prototype.myBind = function(context, ...args){
          //1:保存下当前this
          const _this = this;
          //2.返回一个函数
          return function F(){
              //3:因为返回了一个函数,除了直接调用还可以new F(),所以需要分开判断走
              //4:new的方式
              if(_this instanceof F){
                  return new _this(..args,...arguments);
              }
              //5:直接调用,这里选择了apply的方式实现
      		/**对于参数需要注意一下情况:因为bind可以实现类似这样的代码,f.bind(obj,1)(2),所以我们需要将两边的参数拼接起来,于是就有了这样的实现args.concat(...arguments);*/
              return _this.apply(context,args.concat(...arguments));
          }
      }
      
  53. js的各个位置,比如clientHeight,scrollHeight,offsetHeight,以及scrollTop,offsetTop,clientTop的区别

    • client:说的是可视区域的,也就是元素内部,只包含padding
    • offset:说的是相对容器的位置,包含padding+border
    • scroll:说的是内容层到可视区域的距离
  54. 异步加载js的方法?

    • defer
    • async
  55. Ajax解决浏览器缓存的问题?

    • 使用ajax时,data数据中添加random随机数,
    • 服务器设置响应头来控制浏览器缓存,如Cache-Control: no-cache, no-store, must-revalidate
  56. 节流和防抖

    • 防抖节流本质时优化高频代码的一种手段

    • 防抖: n 秒后在执行该事件,若在 n 秒内被重复触发,则重新计时

    • 节流: n 秒内只运行一次,若在 n 秒内重复触发,只有一次生效

    • 电梯第一个人进来后,15 秒后准时运送一次,这是节流

      电梯第一个人进来后,等待 15 秒。如果过程中又有人进来,15 秒等待重新计时,直到 15 秒后开始运送,这是防抖

    • 代码的实现:

      //节流
      function throttled(fn, delay) {
          let timer = null
          let starttime = Date.now()
          return function () {
              let curTime = Date.now() // 当前时间
              let 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实现;目的都是降低回调执行频率,节省计算资源

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

  57. eval是做什么的?

    • 作用:把字符串参数解析成js代码运行,并返回执行结果
  58. 对象深拷贝的简单实现

    • 浅拷贝只是简简单单地把栈当中的引用地址拷贝了一份,所以当你修改新拷贝出来的值时,被拷贝的对象也会被你修改掉,而深拷贝会在堆内存当中为新对象建立空间,所以被拷贝对象就不会被无缘无故地修改掉了

    • 实现深拷贝:

      //Object.assign:是对对象进行深拷贝的,但是它只对最外层进行,也就是说当对象内嵌套有对象的时候,被嵌套的对象进行的还是浅拷贝
      
      //JSON实现的深拷贝:本质就是会自己构建新的内存来存放新对象
       //注意:忽略undefined、symbol;不能对function进行拷贝
      JSON.parse(JSON.stringify(obj)) 
      
      //MessageChannel
      function deepCopy(obj){
          return new Promise((resolve) =>{
              const {port1,port2} = new MessageChannel();
              port2.onmessage = ev =>resolve(ev.data);
              port1.postMessage(obj);
          })
      }
      deepCopy(obj).then((copy) =>{
          let copyObj = copy;
          console.log(copyObj,obj)
          console.log(copyObj == obj)
      })
      
      //递归实现 (问题出现:不能解决循环引用)
      function cloneDeepDi(obj){
          const newObj = {};
          let keys = Object.keys(obj)
          let key = null;
          let data = null;
          for(let i= 0;i<keys.length;i++){
              key = keys[i];
              data = obj[key];
              if(data && typeof data === "object"){
                  newObj[key] = cloneDeepDi(data)
              }eles{
                  newObj[key] = data
              }
          }
          return newObj
      }
      
      //递归实现升级版:判断一个对象的字段是否引用了这个对象或这个对象的任意父级,如果引用了父级,那么就直接返回同级的新对象,反之,进行递归的那套流程
      function deepCopy (obj,parent = null){
          //创建一个新对象
          let result = {};
          let keys = Object.keys(obj),
              key = null,
              temp = null ,
              _parent = parent;
         	//该字段有父级则需要追溯该字段的父级
          while(_parent){
              //如果该字段引用了它的父级则为循环引用
              if(_parent.originalParent === obj){
                  return _parent.currentParent;
              }
              _parent = _parent.parent;
          }
          
          for(let i=0;i< keys.length;i++){
              key = keys[i];
              temp = obj[key]
              //如果字段的值也是一个对象
              if(temp && temp === "Object"){
                  //递归执行深拷贝,将同级的待拷贝对象与新对象传递给parent 方便循环引用
                  result[key] = deepCopy(temp,{
                      originalParent:obj,
                      currentParent:result,
                      parent:parent
                  });
              }else{
                  result[key] = temp;
              }
          }
          return result;
      }
      
  59. 实现JS中所有对象的深度克隆

    • 梳理数据类型

      1. 原始值类型,包括numberstringbooleannullundefined
      2. 引用值类型,包括objectfunctionarray
      3. symbol
    • 原始值的克隆

      1. 通过for in 遍历出对象身上的所有属性,但是for in 会遍历原型上的
      2. 通过hasOwnProperty过滤掉原型上的属性
      3. 通过typeof除去object、funciton、symbol,剩下都是直接赋值的原始值,包括number、string、Boolean
      function deepClone(origin, target, hash = new WeakMap()) {
          //origin:要被拷贝的对象
          // 需要完善,克隆的结果和之前保持相同的所属类
          var target = target || {};
      
          // 处理特殊情况
          if (origin == null) return origin;  //null 和 undefined 都不用处理
          if (origin instanceof Date) return new Date(origin);
          if (origin instanceof RegExp) return new RegExp(origin);
          if (typeof origin !== 'object') return origin;  // 普通常量直接返回
      
          //  防止对象中的循环引用爆栈,把拷贝过的对象直接返还即可
          if (hash.has(origin)) return hash.get(origin);
          hash.set(origin, target)  // 制作一个映射表
      
          // 拿出所有属性,包括可枚举的和不可枚举的,但不能拿到symbol类型
          var props = Object.getOwnPropertyNames(origin);
          props.forEach((prop, index) => {
              if (origin.hasOwnProperty(prop)) {
                  if (typeof (origin[prop]) === "object") {
                      if (Object.prototype.toString.call(origin[prop]) == "[object Array]") {
                          //数组                            
                          target[prop] = [];
                          deepClone(origin[prop], target[prop], hash);
                      } else if (Object.prototype.toString.call(origin[prop]) == "[object Object]") {
                          //普通对象 
                          target[prop] = {};
      
                          deepClone(origin[prop], target[prop], hash);
                      } else if (origin[prop] instanceof Date) {
                          // 处理日期对象
                          target[prop] = new Date(origin[prop])
                      } else if (origin[prop] instanceof RegExp) {
                          // 处理正则对象
                          target[prop] = new RegExp(origin[prop])
                      } else {
                          //null                                                
                          target[prop] = null;
                      }
                  } else if (typeof (origin[prop]) === "function") {
                      var _copyFn = function (fn) {
                          var result = new Function("return " + fn)();
                          for (var i in fn) {
                              deepClone[fn[i], result[i], hash]
                          }
                          return result
                      }
                      target[prop] = _copyFn(origin[prop]);
                  } else {
                      //除了object、function,剩下都是直接赋值的原始值
                      target[prop] = origin[prop];
                  }
              }
          });
      
          // 单独处理symbol            
          var symKeys = Object.getOwnPropertySymbols(origin);
          if (symKeys.length) {
              symKeys.forEach(symKey => {
                  target[symKey] = origin[symKey];
              });
          }
          return target;
      }
      
  60. 实现一个once函数,传入函数参数只执行一次?

    • 思路:

      function once(fn){
          let ret;
          return funciton (...args){
              if(!fn) return ret;
              ret = fn(...args);
              fn = undefined;
              return ret;
          }
      }
      
  61. 将原生的Ajax封装成promise?

    • //原生ajax
      //创建xmlhttpRequest对象
      let xhr = new XMLHttpRequest();
      //配置请求方式
      xhr.open('get');
      //发送请求
      xhr.send();
      //调用onreadystatechange函数,针对不同的响应状态进行处理
      xhr.onreadstatechange = function(){
          if(xhr.readState === 4){
              if(xhr.status === 200){
                  console.log("成功")
              }else{
                  console.log("失败")
              }
          }
      }
      
      function ajax(url,data = {};type="get"){
      	return new Promise((resolve,reject)=>{
      		$.ajax({
      			type:type,
      			url:url,
      			data:data,
      			datatType:"json",
      			success:function(data){
      				resolve(data);
      			},
      			error:function(err){
      				reject(err);
      			}
      		})
      	})
      }
      
      let url = "http://localhost:5000/login";
      let data = {
          uername:"admin",
          password:"admin"
      }
      let promise = ajax(url,data,"get");
      promise.then(data=>{
          f1();
      }).then(data2 =>{
          f2();
      }).catch(err =>{
          console.log(err);
      })
      
  62. JS监听对象属性的改变

    • 第一种:使用Object.defineProperty();缺点:无法监听到后续新增的属性

      const obj = {
          name:"liukang",
          age:25,
      }
      Object.keys(obj).forEach((key)=>{
          let value = obj[key];
          
          Object.defineProperty(obj,key,{
              get:function(){
                  console.log("我被访问了")
                  return value
              },
              set:function(){
                  console.log("我被设置了")
                  value = newValue
              }
          })
      })
      
    • 第二种:使用Proxy。Es6中新增Proxy类,用于帮助我们创建一个代理的;如果我们希望监听一个对象的相关操作,那么我们可以先创建一个代理对象(Proxy对象),之后对该对象的所有操作,都通过代理对象来完成,代理对象可以监听我们想要对原对象进行哪些操作。

      const obj = {
          name:"liukang",
          age:25,
      }
      const objProxy = new Proxy(obj,{
          //获取值时的捕获器
          get:function(target,key){
              console.log('监听到被访问了');
              return target[key]
          },
          //设置值时的捕获器
          set:function(target,key,newValue){
              console.log('监听到被设置了');
              target[key] = newValue
          },
          //监听in的捕获器
          has:function(target,key){
              console.log('监听到对象的in操作');
              return key in target
          },
          //监听delete的捕获器
          deleteProperty:function(target,key){
              console.log('监听到对象的delete')
          }
      })
      
  63. 如何实现一个私有变量,用get可以访问,不能直接访问

    • 方式一:

      function private(){
          let a = "私有变量";
          this.getName = function(){
              return a;
          }
      }
      let obj = new Private();
      console.log(obj.a) //undefined
      console.log(obj.getName) // "私有变量"
      
    • 方式二:类构造器

      class private2 {
          constructor(){
              let a ='class私有';
              thie.getName = function(){
                  return a;
              }
          }
      }
      
      let p = new private2();
      console.log(p.a) //undefined
      console.log(p.getName())  // "class私有"
      
  64. 怎么控制一次加载一张图片,加载完后在加载下一张

    • 原理:监控图片是否加载完成,就可以了

      //方法一:
      var obj = new Image();
      obj.src = "XXXX";
      obj.onload = function(){
          //加载完成,会执行的函数
      }
      
      //方法二:
      let obj = new Image();
      obj.src = "...";
      obj.onreadystatechange = function(){
          if(this.readyState === 'complate') {
          document.getElementById('mypic').innerHTML = '<img src=" '+this.src +'">'
        }
      }
      
  65. 如何实现sleep的效果

    • sleep函数:是可以使程序暂停执行,等到了指定时间在重新执行,能起到延时的效果。

    • 回调函数方式

      function sleep(callback,time){
          if(typeof callback === 'function'){
              setTimeout(callback,time)
          }
      }
      sleep(function(){console.log("1")},1000)
      
    • Promise方式

      const sleep = time =>{
      	return new Promise(resolve => setTimeout(resolve,time));
      }
      sleep(1000).then(() =>console.log("1"))
      
    • Generator 方式

      function* sleepGenerator(time){
      	yield  new Promise(resolve => setTimeout(resolve,time));
      }
      sleepGenerator(5000).next().value.then(() => console.log("哇哇哇哇哇"))
      
    • await/async 方式

      async function sleep(time){
          await new Promise(resolve => setTimeout(resolve,time));
      }
      sleep(5000).then(() => console.log("当我有个大厂梦"))
      
  66. Function._ proto _(getPrototypeOf)是什么?

    • Function._ proto _ 是获取一个对象的原型,在chrome中 通过_ proto _ 的形式显示。
    • Function._ proto _ == Function.prototype img
  67. 箭头函数中this指向

    • 箭头函数没有自己的this指向,他的this指向上一级作用域的this
    • 箭头函数体内的this对象,就是定义该函数时所在的作用域指向的对象,而不是使用时所在的作用域指向的对象
  68. 数组常用方法有哪些?

    • push:向数组末尾添加数据
    • pop:
    • slice:截取数组中其中一部分
    • splice:截取数组
    • shift:向头部删除数据
    • unshift:向头部添加数据
    • reverse:翻转数组
    • concat:合并数组
    • sort:排序
    • join:数组转字符串
    • indexOf:从左查询数I组中有没有给数据,返回第一次出现的索引
    • lastIndexOf:从右查询数I组中有没有给数据,返回第一次出现的索引
    • forEach:循环遍历
    • map:映射数组的
    • filter:过滤数组
    • every:判断数组是不是满足所有条件
    • some:数组中有没有满足条件的
    • find:用来获取数组中满足条件的第一个数据
    • recude:叠加后的效果
  69. 数组去重有哪些方法?

    • Set + Array.from
    • map +has +set
    • filter+indexOf
    • indexOf
    • includes
  70. 如何去除字符串首尾空格?

    • trim()
    • 正则表达式:reg = /(^ \s* | \s*$)/g + replace
  71. 说说你所知道的JS语言特性?

    • 1、解释性:javascript是一种解释语言,源代码不需要经过编译,直接在浏览器上运行时被解释。
    • 2、跨平台:JavaScript依赖于浏览器本身,与操作环境无关。只要能运行浏览器的计算机,并支持JavaScript的浏览器就可以正确执行。
    • 3、安全性:JavaScript是一种安全性语言。它不允许访问本地的磁盘,并不能将数据存入服务器上;不允许对网络文本进行修改和删除,只能通过浏览器实现信息浏览或动态交互。可有效的防止数据丢失。
    • 4、简单性:JavaScript是一种脚本编写语言,它采用小程序段的方式实现编程,像其它脚本语言一样,JavaScript同样已是一种解释性语言,它提供了一个简易的开发过程。它的基本结构形式与C、C++、VB、Delphi十分类似。但它不像这些语言一样,需要先编译,而是在程序运行过程中被逐行地解释。它与HTML标识结合在一起,从而方便用户的使用操作。
    • 5、动态性:JavaScript是动态的,它可以直接对用户或客户输入做出响应,无须经过Web服务程序。它对用户的反映响应,是采用以事件驱动的方式进行的。所谓事件驱动,就是指在主页中执行了某种操作所产生的动作,就称为“事件”。比如按下鼠标、移动窗口、选择菜单等都可以视为事件。当事件发生后,可能会引起相应的事件响应。
  72. 如何判断一个数组?

    • array.isArray()
    • Object.prototype.toString.call(): Object.prototype.toString.call(a) === '[object Array]'
    • instanceof:用于检验构造函数的prototype属性是否出现在对象的原型链中的任何位置
    • constructor
  73. JS的全排列

    • 全排列:给定一个字符串,输出该字符串所有排列的可能。如输入“abc”,输出“abc,acb,bca,bac,cab,cba”。

    • function fullpermutate(str){
      	var result = [];
          if(str.length > 1){
              for(var m = 0; m < str.length;m++){
                  var left = str[m]
                  var rest = str.slice(0,m) + str.slice(m+1,str.length);
                  var preResult = fullpermutate(rest);
                  for(var i=0;i <preResult.length;i++){
                      var tmp = left + preResult[i]
                      result.push(tmp);
                  }
              }
          }else if(str.length == 1){
              result.push(str);
          }
          
          return result;
      }
      
  74. 谈谈你所理解的跨域,为什么会出项这个问题呢?如何解决

    • 跨域:跨域资源共享,是一种用于当前域的资源被其他域的脚本请求访问的机制,通常由于同源策略,浏览器会禁止这种跨域请求
    • 同源安全策略:是一种约定,是浏览器最核心也是最基本的安全功能,如果缺少了同源策略,浏览器很容易受到XSS,CSRF等攻击。
    • 同源指的是协议、域、端口三者相同,请求可以正常发送和收到,只是被浏览器拦截了
    • 解决跨域:
      1. JSONP:利用script标签没有跨域限制的特点,通过动态生成script标签,指定src属性的方式加载跨域资源。
      2. 配置后端CORS:在服务器端进行配置,加一个 Access-Control-Allow-Origin 响应头
      3. 中间服务器代理
      4. 正向代理是代理服务器对客户端进行代理,为客户端收发请求,使得真实客户端对目标服务器不可见
      5. 反向代理是代理服务器对目标服务器进行代理,为目标服务器进行收发请求,使得真实服务器对客户端不可见
  75. null == undefined 输出什么? null === undefined 又是什么?

    • null == undefined //true :规范中有明确规定,比较时不能将null和undefined转化成其他任何值,并且规定null和undefined是相等的,null和undefined都是无效的值
    • null === undefined //fasle :类型不同
  76. 什么按需加载?

    • 按需加载是前端性能优化的一项重要措施,当用户触发了动作,才加载的对应的功能。
    • 优势:减少不必要的资源请求,节省流量,如果网速不行,则可能会等好长时间
  77. 简单介绍下symbol

    • symbol:独一无二的值
    • 创建symbol对象:const s = Symbol();
    • 接收一个参数比较:Symbol('aaa') != Symbol('aaa')
    • Symbol.for():Symbol.for('foo') === Symbol.for('foo') //true
    • Symbol.keyFor:方法返回一个使用 Symbol.for 方法创建的 symbol 值的 key
  78. 介绍下promise,底层是如何实现?

    • //定义PromseState类,该类包含了Promise对象的三种状态及其对应的操作方法。
      class PromiseState {
        static PENDING = 'pending';  //等待中
        static FULFILLED = 'fulfilled'; //成功
        static REJECTED = 'rejected'; //失败
      
        constructor(state) { //构造函数
          this.state = state || PromiseState.PENDING;
          this.value = null;
          this.reason = null;
          this.fulfillCallbacks = [];
          this.rejectCallbacks = [];
        }
      
          //该方法用于将Promise状态设置为fulfilled
        fulfill(value) {
          if (this.state === PromiseState.PENDING) {
            this.state = PromiseState.FULFILLED;
            this.value = value;
      
            for (let i = 0; i < this.fulfillCallbacks.length; i++) {
              this.fulfillCallbacks[i](value);
            }
          }
        }
      	
          //该方法用于将Promise状态设置为rejected
        reject(reason) {
          if (this.state === PromiseState.PENDING) {
            this.state = PromiseState.REJECTED;
            this.reason = reason;
      
            for (let i = 0; i < this.rejectCallbacks.length; i++) {
              this.rejectCallbacks[i](reason);
            }
          }
        }
      
          //以下:是关于then和catch方法对Promise状态变化的处理
        then(onFulfilled, onRejected) {
          const nextPromiseState = new PromiseState();
      
          if (this.state === PromiseState.PENDING) {
            this.fulfillCallbacks.push((value) => {
              try {
                const x = onFulfilled(value);
                resolvePromise(nextPromiseState, x);
              } catch (e) {
                nextPromiseState.reject(e);
              }
            });
      
            this.rejectCallbacks.push((reason) => {
              try {
                const x = onRejected(reason);
                resolvePromise(nextPromiseState, x);
              } catch (e) {
                nextPromiseState.reject(e);
              }
            });
          } else if (this.state === PromiseState.FULFILLED) {
            setTimeout(() => {
              try {
                const x = onFulfilled(this.value);
                resolvePromise(nextPromiseState, x);
              } catch (e) {
                nextPromiseState.reject(e);
              }
            }, 0);
          } else if (this.state === PromiseState.REJECTED) {
            setTimeout(() => {
              try {
                const x = onRejected(this.reason);
                resolvePromise(nextPromiseState, x);
              } catch (e) {
                nextPromiseState.reject(e);
              }
            }, 0);
          }
      
          return nextPromiseState;
        }
      
        catch(onRejected) {
          return this.then(null, onRejected);
        }
      }
      
      
      //定义了resolvePromise方法,主要用于处理Promise对象的值,当该值为Promise对象时,我们会递归调用then()方法,直到获取到一个非Promise类型的值位置,当该值为其他类型时,我们直接使用fulfill方法,将Promise状态设置为fulfilled
      function resolvePromise(promiseState, x) {
        if (x === promiseState) {
          throw new TypeError('Chaining cycle detected for promise');
        }
      
        if (x && typeof x === 'object' || typeof x === 'function') {
          let used = false;
      
          try {
            const then = x.then;
      
            if (typeof then === 'function') {
              then.call(x, (y) => {
                if (used) {
                  return;
                }
      
                used = true;
                resolvePromise(promiseState, y);
              }, (r) => {
                if (used) {
                  return;
                }
      
                used = true;
                promiseState.reject(r);
              });
            } else {
              promiseState.fulfill(x);
            }
          } catch (e) {
            if (used) {
              return;
            }
      
            used = true;
            promiseState.reject(e);
          }
        } else {
          promiseState.fulfill(x);
        }
      }
      
      
      //最后定义Promise类,该类实现了Promise对象的核心逻辑,并暴露then、catch、static方法。对外部使用者而言,只需要使用new Promise()创建一个Promise对象,并调用then()和catch()方法即可。
      class Promise {
        constructor(fn) {
          if (typeof fn !== 'function') {
            throw new TypeError('Promise resolver ' + fn + ' is not a function');
          }
      
          const state = this.state = new PromiseState();
      
          try {
            fn((value) => {
              resolvePromise(state, value);
            }, (reason) => {
              state.reject(reason);
            })
          } catch (e) {
            state.reject(e);
          }
        }
      
        then(onFulfilled, onRejected) {
          return this.state.then(onFulfilled, onRejected);
        }
      
        catch(onRejected) {
          return this.state.catch(onRejected);
        }
      }
      
      Promise.resolve = function (value) {
        return new Promise((resolve) => {
          resolve(value);
        });
      }
      
      Promise.reject = function (reason) {
        return new Promise((resolve, reject) => {
          reject(reason);
        });
      }
      
  79. js原型链,原型链的顶端是什么?Object的原型是什么?

    • 原型链:由各级对象的_ proto _ 逐级向上引用形成的多级继承关系 在这里插入图片描述
    • 原型链的顶端是Object.prototype._ proto _ = null
    • Object的原型是Objec.prototype;
  80. Promise + Generator +Async的使用

    • Promise

      //Promise的内部错误使用try catch捕获不到,只能只用then的第二个回调或catch来捕获
      //Promise一旦新建就会立即执行,无法取消
      ajax('aaa').then(res=>{
        return ajax('bbb')
      }).then(res=>{
        return ajax('ccc')
      })
      
    • Generator :与函数声明类似,不同的是function关键字与函数名之间有一个星号,以及函数体内部使用yield表达式,定义不同的内部状态(yield在英语里的意思就是“产出”)。

      function* gen(x){
       const y = yield x + 6;
       return y;
      }
      // yield 如果用在另外一个表达式中,要放在()里面
      // 像上面如果是在=右边就不用加()
      function* genOne(x){
        const y = `这是第一个 yield 执行:${yield x + 1}`;
       return y;
      }
      
      const g = gen(1);
      //执行 Generator 会返回一个Object,而不是像普通函数返回return 后面的值
      g.next() // { value: 7, done: false }
      //调用指针的 next 方法,会从函数的头部或上一次停下来的地方开始执行,直到遇到下一个 yield 表达式或return语句暂停,也就是执行yield 这一行
      // 执行完成会返回一个 Object,
      // value 就是执行 yield 后面的值,done 表示函数是否执行完毕
      g.next() // { value: undefined, done: true }
      // 因为最后一行 return y 被执行完成,所以done 为 true
      
      ----------------------------------------------------
      调用Cenerator函数后,该函数并不执行,返回的也不是函数运行结果么,而是 一个指向内部状态的指针对象,也就是遍历对象(Interator Object)。下一步,必须调用遍历器对象的next方法,使得指针移向下一个状态。
      
    • Async/await:Async/await其实就是上面Generator的语法糖,async函数其实就相当于funciton *的作用,而await就相当与yield的作用。而在async/await机制中,自动包含了我们上述封装出来的spawn自动执行函数。

      async function fetch() {
        	await ajax('aaa')
          await ajax('bbb')
          await ajax('ccc')
      }
      
    • 测试题:

      console.log('script start')  
      async function async1() {
          await async2()
          console.log('async1 end')  
      }
      async function async2() {
          console.log('async2 end')  
      }
      async1()
      
      setTimeout(function() {
          console.log('setTimeout')
      }, 0)
      
      new Promise(resolve => {
          console.log('Promise')
          resolve()
      })
      .then(function() {
          console.log('promise1')
      })
      .then(function() {
          console.log('promise2')
      })
      console.log('script end')
      
      
      //打印顺序为
      script start -> async2 end -> Promise -> script end -> async1 end -> promise1 -> promise2 -> setTimeout
      
      //解析
      打印顺序应该是: script start -> async2 end -> Promise -> script end -> async1 end -> promise1 -> promise2 -> setTimeout
      老规矩,全局代码自上而下执行,先打印出script start,然后执行async1(),里面先遇到await async2(),执行async2,打印出async2 end,然后await后面的代码放入微任务队列,接着往下执行new Promise,打印出Promise,遇见了resolve,将第一个then方法放入微任务队列,接着往下执行打印出script end,全局代码执行完了,然后从微任务队列中取出第一个微任务执行,打印出async1 end,再取出第二个微任务执行,打印出promise1,然后这个then方法执行完了,当前Promise的状态为fulfilled,它也可以出发then的回调,所以第二个then这时候又被加进了微任务队列,然后再出微任务队列中取出这个微任务执行,打印出promise2,此时微任务队列为空,接着执行宏任务队列,打印出setTimeout
  81. JS中String 的startwith 和indexOf两种方法的区别

    • startwith:用于检测字符串是否以指定子字符串开始;startsWith()只有两个参数,一个是必须的searchValue,即要匹配的字符串,第二个是可选的position匹配开始位置,不添加第二个参数就是从头开始。返回值是布尔值类型 string.startsWith(searchVlue[, position])
    • 区别:
      1. startwith:返回值为布尔值;表示字符串是否以某一子串开头
      2. indexOf:返回值为字串第一次出现的位置下标,不包含则返回-1
  82. JS字符串转数字的方法

    • parseInt()、parseFloat()
    • Number()
    • 乘数字
  83. 平时 怎么调试JS的

    • console.log、debugger
  84. 怎么获得对象上的属性

    • Object.getOwnPropertyNames();|| Object.getOwnPropertySymbols();
    • for in
    • Object.keys()
    • Reflect.ownKeys
  85. Async和await具体该怎么用?

    • async function ajax(key){
      	return 1
      }
      
      ajax().then(function(res){console.log(res)})
      
      
      async function fn(){
          console.log(1);
          let result = await new Promise(function(resolve,reject){
              setTimeout(function(){
                  resolve(2)
              },5000)
          })
          
          console.log(result)
          console.log(3)
          console.log(await (4))
          console.log(5)
      }
      fn()
      
  86. 知道哪些ES6,ES7的语法?

    • Promise、async/await、let、const、块级作用域、箭头函数、模版字符串、展开运算符、解构赋值等
  87. Promise和async/await的关系

    • 首先他们都是用来处理异步操作的方法,存在互相补充的关系
    • Promise是一种用于处理异步操作的对象,它可以表示一个异步操作的最终完成或失败,并可以在操作完成后获取结果或处理错误。通过Promise,我们可以使用链式调用来处理多个异步操作,使代码更具有可读性。
    • async/await是基于Promise的一种语法糖,他提供一种更直观和同步化的方式来编写异步代码。通过使用async关键字声明一个函数为异步函数,并使用await关键字来等待Promise对象的解决,我们可以以同步的方式编写异步操作的代码
    • Promise可以在其回调函数中使用async/await语法来进行异步操作。通过将异步操作封装在Promise对象中,我们可以使用async/await来更清晰地表达异步操作的顺序和依赖关系。
    • async函数始终返回一个Promise对象,它会自动将函数的返回值包装在一个resolved的Promise中。这意味着我们可以在async函数中使用await来等待其他Promise的完成,并以同步的方式处理它们的结果。
    • 使用async/await可以使异步代码的编写更加简洁和可读。它可以避免嵌套的回调函数和过多的.then()调用,使得异步代码更接近同步代码的写法,提高了代码的可维护性和可理解性
  88. JS加载过程阻塞,解决方法、

    • 场景:默认情况下,浏览器是同步进行加载JavaScript脚本:即渲染引擎遇到script标签就会停下来,等到执行完脚本,再继续向下渲染。如果是外部脚本,还必须加入脚本下载的时间。如果脚本体积过大,下载和执行时间就会很长,因此造成了浏览器堵塞,用户会觉得浏览器很卡,出现短暂空白,没有任何响应,造成不好的体验。
    • 方案:
      1. 改变script标签的位置,最好丢在body标签的最后面,即放在body标签的前面。这种方式不会影响浏览器的DOM渲染,会让页面处理执行完才去执行它
      2. 同步转异步。浏览器允许脚本异步加载,这种方式可以让script标签继续放在head头部,下面是两种异步加载的语法:async和defer
  89. JS对象类型,基本对象类型以及引用对象类型的区别

    • 基本数据类型的值是不可变的,任何方法都不发改变一个基本类型的值,当这个变量重新赋值后看起来变量的值是改变了,但是这里变量名只是指向变量的一个指针,所以改变的是指针的指向,该变量是不变的,但是引用类型可以改变
    • 基本数据类型不可以添加属性和方法,但是引用类型可以
    • 基本数据类型的赋值是简单赋值,如果从一个变量向另一个变量赋值基本类型的值,会在变量对象上创建一个新值,然后把该值复制到为新变量分配的位置上,引用数据类型的赋值是对象引用
    • 基本数据类型的比较是值的比较,引用类型的比较是引用的比较,比较对象的内存地址是否相同
    • 基本数据类型是存在栈中,引用数据类型同时保存在栈区和堆区
  90. 轮播的实现原理?假如一个页面上有两个轮播,你会怎么实现?

  91. 解释一下JS的事件循环

    • 事件循环:处理异步操作的机制
    • 原理:就是将所有异步操作放入一个事件队列中,然后依次执行队列中的任务,直到队列为空为止
    • 在浏览器中分为:
      1. 宏任务:由浏览器提供的异步任务,比如定时器,DOM时间和网络请求等。
      2. 微任务:由JavaScript引擎提供的异步任务,比如Promise的回调函数、MutationObserver的回调函数等。
      3. 渲染阶段
  92. localstorage、sessionStorage、cookie的区别

    • 图片转存失败,建议将图片保存下来直接上传
  93. 解释下HTML5 Drag API

    • 一个典型的拖放操作是这样的:用户选中一个可拖拽的(draggable) 元素,并将其拖拽(鼠标不放开)到一个可放置的(droppable) 元素,然后释放鼠标。

    • 相关API: dragstart:事件主体是被拖放元素,在开始拖放被拖放元素时触发,。

      darg:事件主体是被拖放元素,在正在拖放被拖放元素时触发。

      dragenter:事件主体是目标元素,在被拖放元素进入某元素时触发。

      dragover:事件主体是目标元素,在被拖放在某元素内移动时触发。

      dragleave:事件主体是目标元素,在被拖放元素移出目标元素是触发。

      drop:事件主体是目标元素,在目标元素完全接受被拖放元素时触发。

      dragend:事件主体是被拖放元素,在整个拖放操作结束时触发

  94. 解释下webworker

    • WebWorker是HTML5提供的API,它允许JavaScript运行在后台线程中,独立于主线程,从而实现多线程编程。提高Web应用程序的性能和响应速度
    • 特点:
      1. 独立于主线程
      2. 可以并行执行
      3. 不能直接访问DOM和BOM:可以通过postMessage和onmessage事件与主线程进行通信,从而实现数据的传递和交互
      4. 可以引入外部脚本
  95. {}和[]的valueOf和toString的结果是什么?

    • {}.toString() //[object object] {}.valueOf() // {}
    • [].toString() //"" [].String
    • valueOf返回指定对象的原始值;toString返回一个标识改对象的字符串
  96. 三种事件模型是什么?

    • IE事件模型:只支持冒泡事件
    • DOM事件模型(原始事件):通过元素绑定事件
    • DOM事件模型(冒泡和捕获以及addEventListener)
  97. 介绍一下V8 隐藏类

    • 隐藏类长这样子

      Hidden class 2{
        public string age;
      }
      
              ↑
              |
              |
      
      Hidden class 1{
        public string name;
        if add 'age' transition to class 2
      }
      
              ↑
              |
              |
      
      Hidden class 0{
        if add 'name' transition to class 1
      }
      
      
    • 其次出现原因是由于javascript是动态编程语言,对象在初始化之后,仍然可以对其属性进行增删操作,才会有了隐藏类的概念

    • 作用:为了优化属性的访问速度

    • 保证以相同的顺序实例化对象属性,这样可以保证它们共享相同的隐藏类。

    • 在初始化属性时不要使用动态属性名,推荐使用this.name = name这种方式

    • 面试题: 左右两边都是定义obj1obj2对象,右边的obj2a属性和b属性换了位置,那么左右两边有什么区别呢?

      const obj1 = {     |      const obj1 = {
          a: 1,          |         a: 1,
          b: 2           |         b: 2
      }                  |      }
                         |          
      const obj2 = {     |      const obj2 = {
          a: 1,          |         b: 2,
          b: 2           |         a: 1
      }                  |      }
      
      
      //同样都是生成obj1和obj2,但是左边的结构在创建obj2时复用了执行obj1生成的隐藏类,右侧结构因为a和b的属性定义顺序不相同,则导致了找不到对应的隐藏类,从而要创建出一个新的隐藏类,所以结论就是左侧的代码性能会好于右侧的代码
      
  98. AMD和CMD规范的区别?说一下CommonJS、AMD和CMD?

  99. 谈谈JS的运行机制

    • JavaScript是单线程语言
    • javascript事件循环
    • 宏任务和微任务