前端经典面试题(60道前端面试题包含JS、CSS、React、浏览器等)

简答题

1、什么是防抖和节流?有什么区别?如何实现?

参考答案

防抖

触发高频事件后n秒内函数只会执行一次,如果n秒内高频事件再次被触发,则重新计算时间

每次触发事件时都取消之前的延时调用方法

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

    var inp = document.getElementById('inp');
    inp.addEventListener('input', debounce(sayHi)); // 防抖

节流

高频事件触发,但在n秒内只会执行一次,所以节流会稀释函数的执行频率

每次触发事件时都判断当前是否有等待执行的延时函数

function throttle(fn) {
      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;
        }, 500);
      };
    }
    function sayHi(e) {
      console.log(e.target.innerWidth, e.target.innerHeight);
    }
    window.addEventListener('resize', throttle(sayHi));

2、 get请求传参长度的误区、get和post请求在缓存方面的区别

误区:我们经常说get请求参数的大小存在限制,而post请求的参数大小是无限制的。

参考答案

实际上HTTP 协议从未规定 GET/POST 的请求长度限制是多少。对get请求参数的限制是来源与浏览器或web服务器,浏览器或web服务器限制了url的长度。为了明确这个概念,我们必须再次强调下面几点:

补充补充一个get和post在缓存方面的区别:

3、模块化发展历程

可从IIFE、AMD、CMD、CommonJS、UMD、webpack(require.ensure)、ES Module、

  • 使用 visibility替换 display: none,因为前者只会引起重绘,后者会引发回流(改变了布局)
    把 DOM 离线后修改,比如:先把 DOM 给
    display:none(有一次 Reflow),然后你修改100次,然后再把它显示出来
    不要把 DOM 结点的属性值放在一个循环里当成循环里的变量
    for(let i = 0; i < 1000; i++) {
    // 获取 offsetTop 会导致回流,因为需要去获取正确的值
    console.log(document.querySelector('.test').style.offsetTop)
    }
  • 不要使用 table 布局,可能很小的一个小改动会造成整个 table 的重新布局
  • 动画实现的速度的选择,动画速度越快,回流次数越多,也可以选择使用 requestAnimationFrame
  • CSS 选择符从右往左匹配查找,避免 DOM 深度过深
  • 将频繁运行的动画变为图层,图层能够阻止该节点回流影响别的元素。比如对于 video标签,浏览器会自动将该节点变为图层。
  • react、Vue

    1、写 React / Vue 项目时为什么要在列表组件中写 key,其作用是什么?

    参考答案
    

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

    // vue项目  src/core/vdom/patch.js  -488行
    // 以下是为了阅读性进行格式化后的代码
    
    // oldCh 是一个旧虚拟节点数组
    if (isUndef(oldKeyToIdx)) {
      oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx)
    }
    if(isDef(newStartVnode.key)) {
      // map 方式获取
      idxInOld = oldKeyToIdx[newStartVnode.key]
    } else {
      // 遍历方式获取
      idxInOld = findIdxInOld(newStartVnode, oldCh, oldStartIdx, oldEndIdx)
    }
    

    创建map函数

    function createKeyToOldIdx (children, beginIdx, endIdx) {
      let i, key
      const map = {}
      for (i = beginIdx; i <= endIdx; ++i) {
        key = children[i].key
        if (isDef(key)) map[key] = i
      }
      return map
    }
    

    遍历寻找

    // sameVnode 是对比新旧节点是否相同的函数
     function findIdxInOld (node, oldCh, start, end) {
        for (let i = start; i < end; i++) {
          const c = oldCh[i]
          
          if (isDef(c) && sameVnode(node, c)) return i
        }
      }
    

    2、React 中 setState 什么时候是同步的,什么时候是异步的?

    参考答案
    

    在React中,如果是由React引发的事件处理(比如通过onClick引发的事件处理),调用setState不会同步更新this.state,除此之外的setState调用会同步执行this.state。所谓“除此之外”,指的是绕过React通过addEventListener直接添加的事件处理函数,还有通过setTimeout/setInterval产生的异步调用。

    **原因:**在React的setState函数实现中,会根据一个变量isBatchingUpdates判断是直接更新this.state还是放到队列中回头再说,而isBatchingUpdates默认是false,也就表示setState会同步更新this.state,但是,有一个函数batchedUpdates,这个函数会把isBatchingUpdates修改为true,而当React在调用事件处理函数之前就会调用这个batchedUpdates,造成的后果,就是由React控制的事件处理过程setState不会同步更新this.state

    3、下面输出什么

    class Example extends React.Component {
      constructor() {
        super();
        this.state = {
          val: 0
        };
      }
      
      componentDidMount() {
        this.setState({val: this.state.val + 1});
        console.log(this.state.val);    // 第 1 次 log
    
        this.setState({val: this.state.val + 1});
        console.log(this.state.val);    // 第 2 次 log
    
        setTimeout(() => {
          this.setState({val: this.state.val + 1});
          console.log(this.state.val);  // 第 3 次 log
    
          this.setState({val: this.state.val + 1});
          console.log(this.state.val);  // 第 4 次 log
        }, 0);
      }
    
      render() {
        return null;
      }
    };
    1、第一次和第二次都是在 react 自身生命周期内,触发时 isBatchingUpdates 为 true,所以并不会直接执行更新 state,而是加入了 dirtyComponents,所以打印时获取的都是更新前的状态 0。
    
    2、两次 setState 时,获取到 this.state.val 都是 0,所以执行时都是将 0 设置成 1,在 react 内部会被合并掉,只执行一次。设置完成后 state.val 值为 1。
    
    3、setTimeout 中的代码,触发时 isBatchingUpdates 为 false,所以能够直接进行更新,所以连着输出 2,3。
    
    输出: 0 0 2 3
    

    4、为什么虚拟dom会提高性能?

    参考答案
    

    虚拟dom相当于在js和真实dom中间加了一个缓存,利用dom diff算法避免了没有必要的dom操作,从而提高性能。

    具体实现步骤如下:

    用 JavaScript 对象结构表示 DOM 树的结构;然后用这个树构建一个真正的 DOM 树,插到文档当中

    当状态变更的时候,重新构造一棵新的对象树。然后用新的树和旧的树进行比较,记录两棵树差异

    把2所记录的差异应用到步骤1所构建的真正的DOM树上,视图就更新了。

    css

    1、分析比较 opacity: 0、visibility: hidden、display: none 优劣和适用场景

    参考答案
    

    结构:display:none: 会让元素完全从渲染树中消失,渲染的时候不占据任何空间, 不能点击, visibility: hidden:不会让元素从渲染树消失,渲染元素继续占据空间,只是内容不可见,不能点击 opacity: 0: 不会让元素从渲染树消失,渲染元素继续占据空间,只是内容不可见,可以点击

    继承:display: none:是非继承属性,子孙节点消失由于元素从渲染树消失造成,通过修改子孙节点属性无法显示。visibility: hidden:是继承属性,子孙节点消失由于继承了hidden,通过设置visibility: visible;可以让子孙节点显式。

    性能:displaynone : 修改元素会造成文档回流,读屏器不会读取display: none元素内容,性能消耗较大 visibility:hidden: 修改元素只会造成本元素的重绘,性能消耗较少读屏器读取visibility: hidden元素内容 opacity: 0 :修改元素会造成重绘,性能消耗较少

    联系:它们都能让元素不可见

    2、清除浮动的方式有哪些?比较好的是哪一种?

    参考答案
    

    常用的一般为三种.clearfix, clear:both,overflow:hidden;

    比较好是 .clearfix,伪元素万金油版本,后两者有局限性.

    .clearfix:after {
      visibility: hidden;
      display: block;
      font-size: 0;
      content: " ";
      clear: both;
      height: 0;
    }
    
    
    
    .clearfix:before, .clearfix:after {
        content:"";
        display:table;
    }
    .clearfix:after{
        clear:both;
        overflow:hidden;
    }
    .clearfix{
        zoom:1;
    }
    
    
    

    clear:both:若是用在同一个容器内相邻元素上,那是贼好的,有时候在容器外就有些问题了, 比如相邻容器的包裹层元素塌陷

    overflow:hidden:这种若是用在同个容器内,可以形成 BFC避免浮动造成的元素塌陷

    4、css sprite 是什么,有什么优缺点

    参考答案
    

    概念:将多个小图片拼接到一个图片中。通过 background-position 和元素尺寸调节需要显示的背景图案。

    优点:

    1. 减少 HTTP 请求数,极大地提高页面加载速度
    2. 增加图片信息重复度,提高压缩比,减少图片大小
    3. 更换风格方便,只需在一张或几张图片上修改颜色或样式即可实现

    缺点:

    1. 图片合并麻烦
    2. 维护麻烦,修改一个图片可能需要重新布局整个图片,样式

    5、link与@import的区别

    参考答案
    
    1. link是 HTML 方式, @import是 CSS 方式
    2. link最大限度支持并行下载,@import过多嵌套导致串行下载,出现FOUC
    3. link可以通过rel="alternate stylesheet"指定候选样式
    4. 浏览器对link支持早于@import,可以使用@import对老浏览器隐藏样式
    5. @import必须在样式规则之前,可以在 css 文件中引用其他文件
    6. 总体来说:link 优于@import

    6、display: block;和display: inline;的区别

    参考答案
    

    block元素特点:

    1.处于常规流中时,如果width没有设置,会自动填充满父容器 2.可以应用margin/padding 3.在没有设置高度的情况下会扩展高度以包含常规流中的子元素 4.处于常规流中时布局时在前后元素位置之间(独占一个水平空间) 5.忽略vertical-align

    inline元素特点

    1.水平方向上根据direction依次布局

    2.不会在元素前后进行换行

    3.受white-space控制

    4.margin/padding在竖直方向上无效,水平方向上有效

    5.width/height属性对非替换行内元素无效,宽度由元素内容决定

    6.非替换行内元素的行框高由line-height确定,替换行内元素的行框高由height,margin,padding,border决定 7.浮动或绝对定位时会转换为block8.vertical-align属性生效

    7、容器包含若干浮动元素时如何清理浮动

    参考答案
    
    1. 容器元素闭合标签前添加额外元素并设置clear: both
    2. 父元素触发块级格式化上下文(见块级可视化上下文部分)
    3. 设置容器元素伪元素进行清理推荐的清理浮动方法
    /**
    * 在标准浏览器下使用
    * 1 content内容为空格用于修复opera下文档中出现
    *   contenteditable属性时在清理浮动元素上下的空白
    * 2 使用display使用table而不是block:可以防止容器和
    *   子元素top-margin折叠,这样能使清理效果与BFC,IE6/7
    *   zoom: 1;一致
    **/
    
    .clearfix:before,
    .clearfix:after {
        content: " "; /* 1 */
        display: table; /* 2 */
    }
    
    .clearfix:after {
        clear: both;
    }
    
    /**
    * IE 6/7下使用
    * 通过触发hasLayout实现包含浮动
    **/
    .clearfix {
        *zoom: 1;
    }
    

    8、PNG,GIF,JPG 的区别及如何选

    参考答案
    

    GIF:

    1. 8 位像素,256 色
    2. 无损压缩
    3. 支持简单动画
    4. 支持 boolean 透明
    5. 适合简单动画

    JPEG

    1. 颜色限于 256
    2. 有损压缩
    3. 可控制压缩质量
    4. 不支持透明
    5. 适合照片

    PNG

    1. 有 PNG8 和 truecolor PNG
    2. PNG8 类似 GIF 颜色上限为 256,文件小,支持 alpha 透明度,无动画
    3. 适合图标、背景、按钮

    9、display,float,position 的关系

    参考答案
    
    1. 如果display为 none,那么 position 和 float 都不起作用,这种情况下元素不产生框
    2. 否则,如果 position 值为 absolute 或者 fixed,框就是绝对定位的,float 的计算值为 none,display 根据下面的表格进行调整。
    3. 否则,如果 float 不是 none,框是浮动的,display 根据下表进行调整
    4. 否则,如果元素是根元素,display 根据下表进行调整
    5. 其他情况下 display 的值为指定值 总结起来:绝对定位、浮动、根元素都需要调整display

    10、如何水平居中一个元素

    参考答案
    

    JavaScript

    1、JS有几种数据类型,其中基本数据类型有哪些?

    参考答案
    

    七种数据类型

    (ES6之前)其中5种为基本类型:string,number,boolean,null,undefined,

    ES6出来的Symbol也是原始数据类型 ,表示独一无二的值

    Object为引用类型(范围挺大),也包括数组、函数,

    2、Promise 构造函数是同步执行还是异步执行,那么 then 方法呢?

    参考答案
    const promise = new Promise((resolve, reject) => {
      console.log(1)
      resolve()
      console.log(2)
    })
    
    promise.then(() => {
      console.log(3)
    })
    
    console.log(4)
    

    输出结果是:

    1
    2
    4
    3
    promise构造函数是同步执行的,then方法是异步执行的
    Promise new的时候会立即执行里面的代码 then是微任务 会在本次任务执行完的时候执行 setTimeout是宏任务 会在下次任务执行的时候执行
    

    3、JS的四种设计模式

    参考答案
    

    工厂模式

    简单的工厂模式可以理解为解决多个相似的问题;

    function CreatePerson(name,age,sex) {
        var obj = new Object();
        obj.name = name;
        obj.age = age;
        obj.sex = sex;
        obj.sayName = function(){
            return this.name;
        }
        return obj;
    }
    var p1 = new CreatePerson("longen",'28','男');
    var p2 = new CreatePerson("tugenhua",'27','女');
    console.log(p1.name); // longen
    console.log(p1.age);  // 28
    console.log(p1.sex);  // 男
    console.log(p1.sayName()); // longen
    
    console.log(p2.name);  // tugenhua
    console.log(p2.age);   // 27
    console.log(p2.sex);   // 女
    console.log(p2.sayName()); // tugenhua  
    

    单例模式

    只能被实例化(构造函数给实例添加属性与方法)一次

    // 单体模式
    var Singleton = function(name){
        this.name = name;
    };
    Singleton.prototype.getName = function(){
        return this.name;
    }
    // 获取实例对象
    var getInstance = (function() {
        var instance = null;
        return function(name) {
            if(!instance) {//相当于一个一次性阀门,只能实例化一次
                instance = new Singleton(name);
            }
            return instance;
        }
    })();
    // 测试单体模式的实例,所以a===b
    var a = getInstance("aa");
    var b = getInstance("bb");  
    

    沙箱模式

    将一些函数放到自执行函数里面,但要用闭包暴露接口,用变量接收暴露的接口,再调用里面的值,否则无法使用里面的值

    let sandboxModel=(function(){
        function sayName(){};
        function sayAge(){};
        return{
            sayName:sayName,
            sayAge:sayAge
        }
    })()
    

    发布者订阅模式

    就例如如我们关注了某一个公众号,然后他对应的有新的消息就会给你推送,

    //发布者与订阅模式
        var shoeObj = {}; // 定义发布者
        shoeObj.list = []; // 缓存列表 存放订阅者回调函数
    
        // 增加订阅者
        shoeObj.listen = function(fn) {
            shoeObj.list.push(fn); // 订阅消息添加到缓存列表
        }
    
        // 发布消息
        shoeObj.trigger = function() {
                for (var i = 0, fn; fn = this.list[i++];) {
                    fn.apply(this, arguments);//第一个参数只是改变fn的this,
                }
            }
         // 小红订阅如下消息
        shoeObj.listen(function(color, size) {
            console.log("颜色是:" + color);
            console.log("尺码是:" + size);
        });
    
        // 小花订阅如下消息
        shoeObj.listen(function(color, size) {
            console.log("再次打印颜色是:" + color);
            console.log("再次打印尺码是:" + size);
        });
        shoeObj.trigger("红色", 40);
        shoeObj.trigger("黑色", 42);  
    

    代码实现逻辑是用数组存贮订阅者, 发布者回调函数里面通知的方式是遍历订阅者数组,并将发布者内容传入订阅者数组

    4、列举出集中创建实例的方法

    参考答案
    

    1.字面量

    let obj={'name':'张三'}
    

    2.Object构造函数创建

    let Obj=new Object()
    Obj.name='张三'
    

    3.使用工厂模式创建对象

    function createPerson(name){
     var o = new Object();
     o.name = name;
     };
     return o; 
    }
    var person1 = createPerson('张三');
    

    4.使用构造函数创建对象

    function Person(name){
     this.name = name;
    }
    var person1 = new Person('张三');
    

    5、简述一下前端事件流

    参考答案
    

    HTML中与javascript交互是通过事件驱动来实现的,例如鼠标点击事件onclick、页面的滚动事件onscroll等等,可以向文档或者文档中的元素添加事件侦听器来预订事件。想要知道这些事件是在什么时候进行调用的,就需要了解一下“事件流”的概念。

    什么是事件流:事件流描述的是从页面中接收事件的顺序,DOM2级事件流包括下面几个阶段。

    addEventListeneraddEventListener是DOM2 级事件新增的指定事件处理程序的操作,这个方法接收3个参数:要处理的事件名、作为事件处理程序的函数和一个布尔值。最后这个布尔值参数如果是true,表示在捕获阶段调用事件处理程序;如果是false,表示在冒泡阶段调用事件处理程序。

    IE只支持事件冒泡

    6、Function._proto_(getPrototypeOf)是什么?

    参考答案
    

    获取一个对象的原型,在chrome中可以通过__proto__的形式,或者在ES6中可以通过Object.getPrototypeOf的形式。

    那么Function.proto是什么么?也就是说Function由什么对象继承而来,我们来做如下判别。

    Function.__proto__==Object.prototype //false
    Function.__proto__==Function.prototype//true
    

    我们发现Function的原型也是Function。

    我们用图可以来明确这个关系:


    前端经典面试题(60道前端面试题包含JS、CSS、React、浏览器等)


    7、简述一下原型 / 构造函数 / 实例

    参考答案
    

    这里来举个栗子,以Object为例,我们常用的Object便是一个构造函数,因此我们可以通过它构建实例。

    // 实例
    const instance = new Object()
    

    则此时, 实例为instance, 构造函数为Object,我们知道,构造函数拥有一个prototype的属性指向原型,因此原型为:

    // 原型
    const prototype = Object.prototype
    

    这里我们可以来看出三者的关系:

    实例.__proto__ === 原型
    
    原型.constructor === 构造函数
    
    构造函数.prototype === 原型
    
    // 这条线其实是是基于原型进行获取的,可以理解成一条基于原型的映射线
    // 例如: 
    // const o = new Object()
    // o.constructor === Object   --> true
    // o.__proto__ = null;
    // o.constructor === Object   --> false
    实例.constructor === 构造函数
    

    8、简述一下JS继承,并举例

    参考答案
    

    在 JS 中,继承通常指的便是 原型链继承,也就是通过指定原型,并可以通过原型链继承原型上的属性或者方法。

    9、函数柯里化

    参考答案
    

    在函数式编程中,函数是一等公民。那么函数柯里化是怎样的呢?

    函数柯里化指的是将能够接收多个参数的函数转化为接收单一参数的函数,并且返回接收余下参数且返回结果的新函数的技术。

    函数柯里化的主要作用和特点就是参数复用、提前返回和延迟执行。

    在一个函数中,首先填充几个参数,然后再返回一个新的函数的技术,称为函数的柯里化。通常可用于在不侵入函数的前提下,为函数 预置通用参数,供多次重复调用。

    const add = function add(x) {
        return function (y) {
            return x + y
        }
    }
    
    const add1 = add(1)
    
    add1(2) === 3
    add1(20) === 21
    

    10、说说bind、call、apply 区别?

    参考答案
    

    callapply 都是为了解决改变 this 的指向。作用都是相同的,只是传参的方式不同。

    除了第一个参数外,call 可以接收一个参数列表,apply 只接受一个参数数组。

    let a = {
        value: 1
    }
    function getValue(name, age) {
        console.log(name)
        console.log(age)
        console.log(this.value)
    }
    getValue.call(a, 'yck', '24')
    getValue.apply(a, ['yck', '24'])
    

    bind和其他两个方法作用也是一致的,只是该方法会返回一个函数。并且我们可以通过 bind实现柯里化。

    (下面是对这三个方法的扩展介绍)
    

    如何实现一个 bind 函数

    对于实现以下几个函数,可以从几个方面思考

    Function.prototype.myBind = function (context) {
      if (typeof this !== 'function') {
        throw new TypeError('Error')
      }
      var _this = this
      var args = [...arguments].slice(1)
      // 返回一个函数
      return function F() {
        // 因为返回了一个函数,我们可以 new F(),所以需要判断
        if (this instanceof F) {
          return new _this(...args, ...arguments)
        }
        return _this.apply(context, args.concat(...arguments))
      }
    }
    

    如何实现一个call函数

    Function.prototype.myCall = function (context) {
      var context = context || window
      // 给 context 添加一个属性
      // getValue.call(a, 'yck', '24') => a.fn = getValue
      context.fn = this
      // 将 context 后面的参数取出来
      var args = [...arguments].slice(1)
      // getValue.call(a, 'yck', '24') => a.fn('yck', '24')
      var result = context.fn(...args)
      // 删除 fn
      delete context.fn
      return result
    }
    

    如何实现一个apply函数

    Function.prototype.myApply = function (context) {
      var context = context || window
      context.fn = this
    
      var result
      // 需要判断是否存储第二个参数
      // 如果存在,就将第二个参数展开
      if (arguments[1]) {
        result = context.fn(...arguments[1])
      } else {
        result = context.fn()
      }
    
      delete context.fn
      return result
    }
    

    11、箭头函数的特点

    参考答案
    function a() {
        return () => {
            return () => {
                console.log(this)
            }
        }
    }
    console.log(a()()())
    

    箭头函数其实是没有 this的,这个函数中的 this只取决于他外面的第一个不是箭头函数的函数的 this。在这个例子中,因为调用 a符合前面代码中的第一个情况,所以 thiswindow。并且 this一旦绑定了上下文,就不会被任何代码改变。

    程序阅读题

    1、下面程序输出的结果是什么?

    function sayHi() {
      console.log(name);
      console.log(age);
      var name = "Lydia";
      let age = 21;
    }
    
    sayHi();
    
    参考答案
    

    在函数中,我们首先使用var关键字声明了name变量。这意味着变量在创建阶段会被提升(JavaScript会在创建变量创建阶段为其分配内存空间),默认值为undefined,直到我们实际执行到使用该变量的行。我们还没有为name变量赋值,所以它仍然保持undefined的值。

    使用let关键字(和const)声明的变量也会存在变量提升,但与var不同,初始化没有被提升。在我们声明(初始化)它们之前,它们是不可访问的。这被称为“暂时死区”。当我们在声明变量之前尝试访问变量时,JavaScript会抛出一个ReferenceError

    关于let的是否存在变量提升,我们何以用下面的例子来验证:

    let name = 'ConardLi'
    {
      console.log(name) // Uncaught ReferenceError: name is not defined
      let name = 'code秘密花园'
    }
    

    let变量如果不存在变量提升,console.log(name)就会输出ConardLi,结果却抛出了ReferenceError,那么这很好的说明了,let也存在变量提升,但是它存在一个“暂时死区”,在变量未初始化或赋值前不允许访问。

    变量的赋值可以分为三个阶段:

    关于letvarfunction

    2、下面代码输出什么

    var a = 10;
    (function () {
        console.log(a)
        a = 5
        console.log(window.a)
        var a = 20;
        console.log(a)
    })()
    

    依次输出:undefined -> 10 -> 20

    在立即执行函数中,var a = 20; 语句定义了一个局部变量 a,由于js的变量声明提升机制,局部变量a的声明会被提升至立即执行函数的函数体最上方,且由于这样的提升并不包括赋值,因此第一条打印语句会打印undefined,最后一条语句会打印20。
    
    由于变量声明提升,a = 5; 这条语句执行时,局部的变量a已经声明,因此它产生的效果是对局部的变量a赋值,此时window.a 依旧是最开始赋值的10,
    

    3、下面的输出结果是什么?

    class Chameleon {
      static colorChange(newColor) {
        this.newColor = newColor;
      }
    
      constructor({ newColor = "green" } = {}) {
        this.newColor = newColor;
      }
    }
    
    const freddie = new Chameleon({ newColor: "purple" });
    freddie.colorChange("orange");
    

    答案: D

    colorChange方法是静态的。静态方法仅在创建它们的构造函数中存在,并且不能传递给任何子级。由于freddie是一个子级对象,函数不会传递,所以在freddie实例上不存在freddie方法:抛出TypeError

    4、下面代码中什么时候会输出1?

    var a = ?;
    if(a == 1 && a == 2 && a == 3){
         conso.log(1);
    }
    参考答案
    

    因为==会进行隐式类型转换 所以我们重写toString方法就可以了

    var a = {
      i: 1,
      toString() {
        return a.i++;
      }
    }
    
    if( a == 1 && a == 2 && a == 3 ) {
      console.log(1);
    }
    

    5、下面的输出结果是什么?

    var obj = {
        '2': 3,
        '3': 4,
        'length': 2,
        'splice': Array.prototype.splice,
        'push': Array.prototype.push
    }
    obj.push(1)
    obj.push(2)
    console.log(obj)
    参考答案
    

    1.使用第一次push,obj对象的push方法设置 obj[2]=1;obj.length+=12.使用第二次push,obj对象的push方法设置 obj[3]=2;obj.length+=13.使用console.log输出的时候,因为obj具有 length 属性和 splice 方法,故将其作为数组进行打印 4.打印时因为数组未设置下标为 0 1 处的值,故打印为empty,主动 obj[0] 获取为 undefined

    前端经典面试题(60道前端面试题包含JS、CSS、React、浏览器等)

    6、下面代码输出的结果是什么?

    var a = {n: 1};
    var b = a;
    a.x = a = {n: 2};
    
    console.log(a.x)     
    console.log(b.x)
    参考答案
    

    undefined {n:2}

    首先,a和b同时引用了{n:2}对象,接着执行到a.x = a = {n:2}语句,尽管赋值是从右到左的没错,但是.的优先级比=要高,所以这里首先执行a.x,相当于为a(或者b)所指向的{n:1}对象新增了一个属性x,即此时对象将变为{n:1;x:undefined}。之后按正常情况,从右到左进行赋值,此时执行a ={n:2}的时候,a的引用改变,指向了新对象{n:2},而b依然指向的是旧对象。之后执行a.x = {n:2}的时候,并不会重新解析一遍a,而是沿用最初解析a.x时候的a,也即旧对象,故此时旧对象的x的值为{n:2},旧对象为 {n:1;x:{n:2}},它被b引用着。后面输出a.x的时候,又要解析a了,此时的a是指向新对象的a,而这个新对象是没有x属性的,故访问时输出undefined;而访问b.x的时候,将输出旧对象的x的值,即{n:2}。

    7、下面代码的输出是什么?

    function checkAge(data) {
      if (data === { age: 18 }) {
        console.log("You are an adult!");
      } else if (data == { age: 18 }) {
        console.log("You are still an adult.");
      } else {
        console.log(`Hmm.. You don't have an age I guess`);
      }
    }
    
    checkAge({ age: 18 });
    参考答案
    

    Hmm.. You don't have an age I guess

    在比较相等性,原始类型通过它们的值进行比较,而对象通过它们的引用进行比较。JavaScript检查对象是否具有对内存中相同位置的引用。

    我们作为参数传递的对象和我们用于检查相等性的对象在内存中位于不同位置,所以它们的引用是不同的。

    这就是为什么{ age: 18 } === { age: 18 }{ age: 18 } == { age: 18 }返回 false的原因。

    8、下面代码的输出是什么?

    const obj = { 1: "a", 2: "b", 3: "c" };
    const set = new Set([1, 2, 3, 4, 5]);
    
    obj.hasOwnProperty("1");
    obj.hasOwnProperty(1);
    set.has("1");
    set.has(1);
    参考答案
    

    true` `true` `false` `true

    所有对象键(不包括Symbols)都会被存储为字符串,即使你没有给定字符串类型的键。这就是为什么obj.hasOwnProperty('1')也返回true

    上面的说法不适用于Set。在我们的Set中没有“1”set.has('1')返回false。它有数字类型1set.has(1)返回true

    9、下面代码的输出是什么?

    // example 1
    var a={}, b='123', c=123;  
    a[b]='b';
    a[c]='c';  
    console.log(a[b]);
    
    ---------------------
    // example 2
    var a={}, b=Symbol('123'), c=Symbol('123');  
    a[b]='b';
    a[c]='c';  
    console.log(a[b]);
    
    ---------------------
    // example 3
    var a={}, b={key:'123'}, c={key:'456'};  
    a[b]='b';
    a[c]='c';  
    console.log(a[b]);
    参考答案
    

    这题考察的是对象的键名的转换。

    // example 1
    var a={}, b='123', c=123;
    a[b]='b';
    // c 的键名会被转换成字符串'123',这里会把 b 覆盖掉。
    a[c]='c';  
    // 输出 c
    console.log(a[b]);
    
    
    // example 2
    var a={}, b=Symbol('123'), c=Symbol('123');  
    // b 是 Symbol 类型,不需要转换。
    a[b]='b';
    // c 是 Symbol 类型,不需要转换。任何一个 Symbol 类型的值都是不相等的,所以不会覆盖掉 b。
    a[c]='c';
    // 输出 b
    console.log(a[b]);
    
    
    // example 3
    var a={}, b={key:'123'}, c={key:'456'};  
    // b 不是字符串也不是 Symbol 类型,需要转换成字符串。
    // 对象类型会调用 toString 方法转换成字符串 [object Object]。
    a[b]='b';
    // c 不是字符串也不是 Symbol 类型,需要转换成字符串。
    // 对象类型会调用 toString 方法转换成字符串 [object Object]。这里会把 b 覆盖掉。
    a[c]='c';  
    // 输出 c
    console.log(a[b]);
    

    10、下面代码的输出是什么?

    (() => {
      let x, y;
      try {
        throw new Error();
      } catch (x) {
        (x = 1), (y = 2);
        console.log(x);
      }
      console.log(x);
      console.log(y);
    })();
    参考答案
    

    1` `undefined` `2

    catch块接收参数x。当我们传递参数时,这与变量的x不同。这个变量x是属于catch作用域的。

    之后,我们将这个块级作用域的变量设置为1,并设置变量y的值。现在,我们打印块级作用域的变量x,它等于1

    catch块之外,x仍然是undefined,而y2。当我们想在catch块之外的console.log(x)时,它返回undefined,而y返回2

    11、下面代码的输出结果是什么?

    function Foo() {
        Foo.a = function() {
            console.log(1)
        }
        this.a = function() {
            console.log(2)
        }
    }
    Foo.prototype.a = function() {
        console.log(3)
    }
    Foo.a = function() {
        console.log(4)
    }
    Foo.a();
    let obj = new Foo();
    obj.a();
    Foo.a();
    参考答案
    

    输出顺序是 4 2 1

    function Foo() {
        Foo.a = function() {
            console.log(1)
        }
        this.a = function() {
            console.log(2)
        }
    }
    // 以上只是 Foo 的构建方法,没有产生实例,此刻也没有执行
    
    Foo.prototype.a = function() {
        console.log(3)
    }
    // 现在在 Foo 上挂载了原型方法 a ,方法输出值为 3
    
    Foo.a = function() {
        console.log(4)
    }
    // 现在在 Foo 上挂载了直接方法 a ,输出值为 4
    
    Foo.a();
    // 立刻执行了 Foo 上的 a 方法,也就是刚刚定义的,所以
    // # 输出 4
    
    let obj = new Foo();
    /* 这里调用了 Foo 的构建方法。Foo 的构建方法主要做了两件事:
    1. 将全局的 Foo 上的直接方法 a 替换为一个输出 1 的方法。
    2. 在新对象上挂载直接方法 a ,输出值为 2。
    */
    
    obj.a();
    // 因为有直接方法 a ,不需要去访问原型链,所以使用的是构建方法里所定义的 this.a,
    // # 输出 2
    
    Foo.a();
    // 构建方法里已经替换了全局 Foo 上的 a 方法,所以
    // # 输出 1

    作者:静观流叶

    原文地址:https://mp.weixin.qq.com/s/PTSaytcf3xgOp6C9l3Pvjw

    展开阅读全文

    页面更新:2024-04-27

    标签:赋值   数组   原型   字符串   变量   函数   实例   属性   元素   浏览器   对象   参数   类型   事件   代码   方法   经典   科技

    1 2 3 4 5

    上滑加载更多 ↓
    推荐阅读:
    友情链接:
    更多:

    本站资料均由网友自行发布提供,仅用于学习交流。如有版权问题,请与我联系,QQ:4156828  

    © CopyRight 2020-2024 All Rights Reserved. Powered By 71396.com 闽ICP备11008920号-4
    闽公网安备35020302034903号

    Top