乐趣区

关于前端:必知必会的JavaScript前端面试题篇一不看后悔

前言

系列首发于公众号『前端进阶圈』,若不想错过更多精彩内容,请“星标”一下,敬请关注公众号最新消息。

必知必会的 JavaScript 前端面试题篇 (一),不看悔恨!

1. 对 this 对象的了解?

  • 定义:在执行上下文中的一个属性,它指向最初一次调用这个属性或办法的对象。通常有四种状况来判断。
  • 严格模式中应用 use strict

    1. 函数调用模式:当一个函数不是一个对象的属性时,间接作为函数来调用时,严格模式下指向 undefined, 非严格模式下,this 指向全局对象。
    // 严格模式
    'use strict';
    var name = 'window';
    var doSth = function () {console.log(typeof this === 'undefined');
        console.log(this.name);
    };
    doSth(); // true,// 报错,因为 this 是 undefined
    
    // 非严格模式
    let name2 = 'window2';
    let doSth2 = function () {console.log(this === window);
        console.log(this.name2);
    };
    doSth2(); // true, undefined
    1. 办法调用模式:如果一个函数作为一个对象的办法来调用时,this 指向以后这个对象
    var name = 'window';
    var doSth = function () {console.log(this.name);
    };
    var student = {
        name: 'lc',
        doSth: doSth,
        other: {
            name: 'other',
            doSth: doSth,
        },
    };
    student.doSth(); // 'lc'
    student.other.doSth(); // 'other'
    // 用 call 类比则为:student.doSth.call(student);
    // 用 call 类比则为:student.other.doSth.call(student.other);
    1. 结构器调用模式:如果一个函数通过 new 调用时,函数执行前会新创建一个对象,this 指向这个新创建的对象。
    var Obj = function (p) {this.p = p;};
    
    var o = new Obj('Hello World!');
    o.p; // "Hello World!"
    1. apply, call, bind 模式:显示更改 this 指向,严格模式下,指向绑定的第一个参数,非严格模式下,null 和 undefined 指向全局对象(浏览器中是 window),其余指向被 new Object() 包裹的对象。

      • apply: apply(this 绑定的对象,参数数组) func.apply(thisValue, [arg1, arg2, ...])
      function f(x, y) {console.log(x + y);
      }
      
      f.call(null, 1, 1); // 2
      f.apply(null, [1, 1]); // 2
      • call: call(this 绑定的对象,一个个参数) func.call(thisValue, arg1, arg2, ...)
      var doSth = function (name) {console.log(this);
          console.log(name);
      };
      doSth.call(2, 'lc'); // Number{2}, 'lc'
      var doSth2 = function (name) {
          'use strict';
          console.log(this);
          console.log(name);
      };
      doSth2.call(2, 'lc'); // 2, 'lc'
      • bind: bind(this 绑定的对象) func.bind(thisValue)
      var counter = {
          count: 0,
          inc: function () {this.count++;},
      };
      
      var obj = {count: 100,};
      var func = counter.inc.bind(obj);
      func();
      obj.count; // 101
      
      // eg2:var add = function (x, y) {return x * this.m + y * this.n;};
      
      var obj = {
          m: 2,
          n: 2,
      };
      
      var newAdd = add.bind(obj, 5);
      newAdd(5); // 20
  • 箭头函数:不会应用以上准则,而是依据以后作用域来决定 this, 也就是说箭头函数会继承外层函数,调用的 this 绑定,没有外层函数,则是指向全局(浏览器中是 window)。
  • 优先级: 结构器模式 > apply, call, bind > 办法调用模式 > 函数调用模式

2. 在地址栏中输出网址,按下回车网页产生了什么?

  1. 解析 URL: 剖析所应用的协定和申请资源的门路
  2. 缓存判断 :判断以后申请资源是否存在缓存中,如果在缓存中且没有生效,间接返回,否则向服务器发动申请。
  3. DNS 解析 :从 URL 中解析出 IP 地址,而后判断本地是否有缓存,有则应用,没有则向本地 DNS 服务器发动申请,先判断是否存在缓存,没有则向跟域名服务器发动申请,最终取得域名 IP 地址后,本地 DNS 服务器再将这个 IP 返回给用户。
  4. 获取 MAC 地址 :获取到 IP 地址后,数据传输还需目标主机的 MAC 地址,应用层下发数据给传输层,TCP 协定会执行端口号和目标端口号,而后下发数据给网络层。
  5. TCP 三次握手 :首先客户端向服务器发送一个 SYN 连贯申请报文段和一个随机序号,服务端接管到申请后向服务器端发送一个 SYN ACK 报文段,确认连贯申请,并且也向客户端发送一个随机序号。客户端接管服务器的确认应答后,进入连贯建设的状态,同时向服务器也发送一个 ACK 确认报文段,服务器端接管到确认后,也进入连贯建设状态,此时单方的连贯就建设起来了。
  6. HTTPS 握手 :如果应用的是 HTTPS 协定,在通信前还存在 TLS 的一个四次握手的过程。首先由客户端向服务器端发送应用的协定的版本号、一个随机数和能够应用的加密办法。服务器端收到后,确认加密的办法,也向客户端发送一个随机数和本人的数字证书。客户端收到后,首先查看数字证书是否无效,如果无效,则再生成一个随机数,并应用证书中的公钥对随机数加密,而后发送给服务器端,并且还会提供一个后面所有内容的 hash 值供服务器端测验。服务器端接管后,应用本人的私钥对数据解密,同时向客户端发送一个后面所有内容的 hash 值供客户端测验。这个时候单方都有了三个随机数,依照之前所约定的加密办法,应用这三个随机数生成一把秘钥,当前单方通信前,就应用这个秘钥对数据进行加密后再传输。
  7. 返回数据 :当页面申请发送到服务器端后,服务器端会返回一个 html 文件作为响应,浏览器接管到响应后,开始对 html 文件进行解析,开始页面的渲染过程。
  8. 页面渲染 : 浏览器首先会依据 html 文件构建 DOM 树,依据解析到的 css 文件构建 CSSOM 树,如果遇到 script 标签,则判端是否含有 defer 或者 async 属性,要不然 script 的加载和执行会造成页面的渲染的阻塞。当 DOM 树和 CSSOM 树建设好后,依据它们来构建渲染树。渲染树构建好后,会依据渲染树来进行布局。布局实现后,最初调用 paint 办法对页面进行绘制。这个时候整个页面就显示进去了。
  9. TCP 四次挥手 : 最初一步是 TCP 断开连接的四次挥手过程。若客户端认为数据发送实现,则它须要向服务端发送连贯开释申请。服务端收到连贯开释申请后,会通知应用层要开释 TCP 链接。而后会发送 ACK 包,并进入 CLOSE_WAIT 状态,此时表明客户端到服务端的连贯曾经开释,不再接管客户端发的数据了。然而因为 TCP 连贯是双向的,所以服务端仍旧能够发送数据给客户端。服务端如果此时还有没发完的数据会持续发送,结束后会向客户端发送连贯开释申请,而后服务端便进入 LAST-ACK 状态。客户端收到开释申请后,向服务端发送确认应答,此时客户端进入 TIME-WAIT 状态。该状态会继续 2MSL(最大段生存期,指报文段在网络中生存的工夫,超时会被摈弃)工夫,若该时间段内没有服务端的重发申请的话,就进入 CLOSED 状态。当服务端收到确认应答后,也便进入 CLOSED 状态。
  • S:

    1. 解析 url: 例如协定,域名,端口,门路,参数等
    2. 缓存判断:依据 url 先判断本地 DNS 服务器中是否有该 url 的 IP 地址,如果有,则间接应用,如果没有,则申请服务器
    3. 获取 IP 地址:依据域名去 DNS 服务器查问对应的 IP 地址
    4. 获取 mac 地址:因为数据传输须要主机的 mac 地址
    5. TCP 三次握手:次要的过程就是与服务器建设连贯
    6. HTTPS 握手:如果协定是 https,还须要一个四次握手的过程,次要是判断版本号、数字证书和 hash 值是否无效,因为后续是通过这三个的加密数据进行传输的
    7. 服务器响应解决并返回数据
    8. 浏览器解析成可执行的文件并渲染
    9. TCP 四次挥手:TCP 断开链接的一个过程,若客户端认为数据曾经传输实现的后,客户端会发送一个开释申请给服务器,而后 TCP 会进入 CLOSE_WAIT 状态,这个状态最大会继续 2MSL(2 倍的最长报文段寿命, 通常 1MSL 为 2 分钟也就是 120s, 则 2MSL 工夫大略为 4 分钟 240s) 工夫,如果这个时间段客户端没有再向服务器发送申请,则会进入 CLOSE 状态。

3. 对象创立的形式有哪些?

  1. 工厂模式:通过一个函数来封装创建对象的过程。只是简略的封装了复用代码,没有建设起对象与类型之间的关系。
// 工厂函数 通过扭转参数屡次应用
function Person(name, age) {const obj = {};
    obj.name = name;
    obj.age = age;
    return obj;
}

const person = Person('dz', 23);
const person1 = Person('dz1', 24);
console.log(person instanceof Person); // -> false
console.log(person1.__proto__ == person.__proto_); // -> false
  1. 构造函数模式:通过 new 关键字来创立。但会造成不必要的函数对象的创立。因为在 js 中函数也是一个对象,因而如果对象属性中如果蕴含函数的话,那就会每次都新建一个对象,节约了不必要的内存空间,因为在函数中所有的实例都能够通用的。
// 内置构造函数创建对象
function Person(name, age) {
    this.name = name;
    this.age = age;
    this.sayname = () => {console.log(this.name);
    };
}

const p1 = new Person('dz', 23);
const p2 = new Person('dz1', 24);
console.log(p1 instanceof Person, p2 instanceof Person); // --> true true
  1. 原型模式:因为每一个函数都有一个 prototype 属性,这个属性是一个对象,它蕴含了通过构造函数创立的所有实例都能共享的属性和办法。
function Person() {}
Person.prototype.name = 'Nike';
Person.prototype.age = 20;
Person.prototype.jbo = 'teacher';
Person.prototype.sayName = function () {alert(this.name);
};
var person1 = new Person();
person1.sayName();
  1. 构造函数 + 原型模式:每个实例领有本人的属性和办法, 以及共享雷同的办法, 用的较多一种模式
function Person(name, age) {
    this.name = name;
    this.age = age;
}

Person.prototype.sayname = () => {console.log(this.name);
};

const p1 = new Person('dz', 23);
const p2 = new Person('dz1', 24);
console.log(p1.name, p2.name); // dz dz1
  1. 动静原型模式:这一种模式将原型办法赋值的创立过程挪动到了构造函数的外部,通过对属性是否存在的判断,能够实现仅在第一次调用函数时对原型对象赋值一次的成果。这一种形式很好地对下面的混合模式进行了封装。
function Person(name, age) {
    this.name = name;
    this.age = age;
    if (typeof this.sayname != 'function') {Person.prototype.sayname = () => {console.log(this.name);
        };
    }
}
const p1 = new Person('dz', 23);
console.log(p1.sayname); // -> dz
  1. 寄生构造函数模式:

    • 根本思维:这一种模式和工厂模式的实现基本相同。它次要是基于一个已有的类型,在实例化时对实例化的对象进行扩大。这样既不必批改原来的构造函数,也达到了扩大对象的目标。它的一个毛病和工厂模式一样,无奈实现对象的辨认。
function SpecialArray() {var array = new Array();
    array.push.apply(array, arguments);
    array.toPipedString = function () {return this.join('|');
    };
    return array;
}
var colors = new SpecialArray('red', 'green', 'pink');
alert(colors.toPipedString()); // red|green|pink
alert(colors instanceof SpecialArray); // false
  1. class 创建对象:constructor 是构造方法, 相似构造函数, 定义这个办法外面的内容都是实例本身的属性和办法, 不会被其余实例共享, 而写在里面的 sayname 示意原型上的办法, 是会被共享的。
class Person {constructor(name, age) { // constructor 构造函数
        this.name = name
        this.age  = age
    }

    sayname() { // 原型上的
        console.log(this.name)
    }

    static sayAge() {console.log(this.age)
    }
}

const per = new Person('dz', 23)
per.sayname() // -> dz
Person.sayAge() // 23


// static 示意动态,加了 static 的函数不会挂载到 prototype 上, 而是挂载到 class 类 上, 相似于:
Promise.resolve(...)
Math.max(...)

4. javascript 为什么要进行变量晋升,它导致了什么问题?

  • 无论是函数还是变量在那个地位上申明,都会被晋升到函数之前,可保障变量申明前可拜访而不会报错
  • 变量晋升的实质:js 引擎在代码执行前有一个解析的过程,会创立一个执行上下文,初始化一些代码执行所须要的参数。当拜访一个变量时,会在以后执行上下文的作用域链中去查找,而作用域链的首端指向的是以后执行上下文的变量对象,这个变量对象是执行上下文的一个属性,它蕴含了函数的形参,所有函数和变量申明,这个对象是在代码解析的时候创立的。
  • 代码执行过程:

    1. 解析阶段:JS 会查看语法,并对函数进行预编译。解析的时候会先创立一个全局执行上下文环境,先把代码中行将执行的变量、函数申明都拿进去,变量先赋值为 undefined,函数先申明好可应用。在一个函数执行之前,也会创立一个函数执行上下文环境,跟全局执行上下文相似,不过函数执行上下文会多出 this、arguments 和函数的参数。

      • 全局上下文:变量定义,函数申明
      • 函数上下文:变量定义,函数申明,this,arguments
    2. 执行阶段:依照代码程序执行
  • 为什么要进行变量晋升?

    1. 进步性能:在代码执行前,会进行语法检查和预编译,这个操作只会执行一次,这么做就是为了进步性能,如果没有这一步,则每次执行代码前都必须从新编译一下变量和函数,这是没有必要的,因为变量和函数的代码根本不会扭转,解析一遍就够了。
    2. 容错性更好:

      a = 1;
      var a;
      console.log(a);
  • 总结:

    • 解析和预编译过程中的申明晋升能够进步性能,让函数能够在执行时事后为变量调配栈空间
    • 申明晋升还能够进步 JS 代码的容错性,使一些不标准的代码也能够失常执行
  • 导致的问题:

    var tmp = new Date();
    
    function fn() {console.log(tmp);
        if (false) {var tmp = 'hello world';}
    }
    
    fn(); // undefined
    
    // 在这个函数中,本来是要打印出外层的 tmp 变量,然而因为变量晋升的问题,内层定义的 tmp 被提到函数外部的最顶部,相当于笼罩了外层的 tmp,所以打印后果为 undefined。
    for (var i = 0; i < 3; i++) {setTimeout(() => {console.log(i);
        }, 2000);
    } // 3 3 3
    
    for (let i = 0; i < 3; i++) {setTimeout(() => {console.log(i);
        }, 2000);
    } // 0 1 2
    
    // 因为遍历时定义的 i 会变量晋升成为一个全局变量,在函数完结之后不会被销毁,所以,始终批改的是之前的定义的全局变量,所以第一个输入三次 3, 第二个输入 0 1 2。// 在 for 循环中,let 申明的变量会存在一个块级作用域的概念,应用 let 申明的迭代变量时,js 引擎会在后盾为每一个迭代循环申明一个新的迭代变量,因而每次应用的 i 都是不同的。

5. var, let, const 的区别?

区别 var let const
是否有块级作用域 {} 包裹 ✔️ ✔️
是否存在变量晋升 ✔️
是否增加全局属性 ✔️
是否反复申明变量 ✔️
是否存在暂时性死区 ✔️ ✔️
是否必须设置初始值 ✔️
是否扭转指针指向 ✔️ ✔️
  • 暂时性死区:在应用 let、const 命令申明变量之前,该变量都是不可用的。

特殊字符形容:

  1. 问题标注 Q:(question)
  2. 答案标注 R:(result)
  3. 注意事项规范:A:(attention matters)
  4. 详情形容标注:D:(detail info)
  5. 总结标注:S:(summary)
  6. 剖析标注:Ana:(analysis)
  7. 提醒标注:T:(tips)

    往期举荐:

  8. 前端面试实录 HTML 篇
  9. 前端面试实录 CSS 篇
  10. JS 如何判断一个元素是否在可视区域内?
  11. Vue2、3 生命周期及作用?
  12. 排序算法:QuickSort
  13. 箭头函数与一般函数的区别?
  14. 这是你了解的 CSS 选择器权重吗?
  15. JS 中 call, apply, bind 概念、用法、区别及实现?
  16. 罕用位运算办法?
  17. Vue 数据监听 Object.definedProperty() 办法的实现
  18. 为什么 0.1+ 0.2 != 0.3,如何让其相等?
  19. 聊聊对 this 的了解?
  20. JavaScript 为什么要进行变量晋升,它导致了什么问题?

    最初:

  21. 欢送关注『前端进阶圈』公众号,一起摸索学习前端技术 ……
  22. 公众号回复 加群 或 扫码, 即可退出前端交流学习群,一起高兴摸鱼和学习 ……
  23. 公众号回复 加好友,即可添加为好友
退出移动版