关于前端:字节前端高频面试题

2次阅读

共计 10959 个字符,预计需要花费 28 分钟才能阅读完成。

什么是原型什么是原型链?

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>

</body>
<script>
    function Person () {}    var person  = new Person();    person.name = 'Kevin';    console.log(person.name) // Kevin

    // prototype
    function Person () {}    Person.prototype.name = 'Kevin';    var person1 = new Person();    var person2 = new Person();    console.log(person1.name)// Kevin
    console.log(person2.name)// Kevin

    // __proto__
    function Person () {}    var person = new Person();    console.log(person.__proto__ === Person.prototype) // true

    //constructor
    function Person() {}    console.log(Person === Person.prototype.constructor) // true

    // 综上所述
    function Person () {}    var person = new Person()    console.log(person.__proto__ == Person.prototype) // true
    console.log(Person.prototype.constructor == Person) // true
    // 顺便学习一下 ES5 得办法, 能够取得对象得原型
    console.log(Object.getPrototypeOf(person) === Person.prototype) // true

    // 实例与原型
    function Person () {}    Person.prototype.name = 'Kevin';    var person = new Person();    person.name = 'Daisy';    console.log(person.name) // Daisy
    delete person.name;    console.log(person.name) // Kevin

    // 原型得原型
    var obj = new Object();    obj.name = 'Kevin',    console.log(obj.name) //Kevin

     // 原型链
     console.log(Object.prototype.__proto__ === null) //true
     // null 示意 "没用对象" 即该处不应该有值

     // 补充
     function Person() {}     var person = new Person()     console.log(person.constructor === Person) // true
     // 当获取 person.constructor 时,其实 person 中并没有 constructor 属性, 当不能读取到 constructor 属性时, 会从 person 的原型
     // 也就是 Person.prototype 中读取时, 正好原型中有该属性, 所以
     person.constructor === Person.prototype.constructor

     //__proto__
     // 其次是__proto__,绝大部分浏览器都反对这个非标准的办法拜访原型,然而它并不存在于 Person.prototype 中,实际上,它
     // 是来自与 Object.prototype, 与其说是一个属性,不如说是一个 getter/setter, 当应用 obj.__proto__时,能够了解成返回了
     // Object.getPrototypeOf(obj)
     总结:1、当一个对象查找属性和办法时会从本身查找, 如果查找不到则会通过__proto__指向被实例化的构造函数的 prototype     2、隐式原型也是一个对象, 是指向咱们构造函数的原型     3、除了最顶层的 Object 对象没有__proto_,其余所有的对象都有__proto__, 这是隐式原型     4、隐式原型__proto__的作用是让对象通过它来始终往上查找属性或办法,直到找到最顶层的 Object 的__proto__属性,它的值是 null, 这个查找的过程就是原型链



</script>
</html>

forEach 和 map 办法有什么区别

这办法都是用来遍历数组的,两者区别如下:

  • forEach()办法会针对每一个元素执行提供的函数,对数据的操作会扭转原数组,该办法没有返回值;
  • map()办法不会扭转原数组的值,返回一个新数组,新数组中的值为原数组调用函数解决之后的值;

JSONP

JSONP 外围原理:script 标签不受同源策略束缚,所以能够用来进行跨域申请,长处是兼容性好,然而只能用于 GET 申请;

const jsonp = ({url, params, callbackName}) => {const generateUrl = () => {
        let dataSrc = ''
        for (let key in params) {if (params.hasOwnProperty(key)) {dataSrc += `${key}=${params[key]}&`
            }
        }
        dataSrc += `callback=${callbackName}`
        return `${url}?${dataSrc}`
    }
    return new Promise((resolve, reject) => {const scriptEle = document.createElement('script')
        scriptEle.src = generateUrl()
        document.body.appendChild(scriptEle)
        window[callbackName] = data => {resolve(data)
            document.removeChild(scriptEle)
        }
    })
}

说一下原型链和原型链的继承吧

  • 所有一般的 [[Prototype]] 链最终都会指向内置的 Object.prototype,其蕴含了 JavaScript 中许多通用的性能
  • 为什么能创立“类”,借助一种非凡的属性:所有的函数默认都会领有一个名为 prototype 的共有且不可枚举的属性,它会指向另外一个对象,这个对象通常被称为函数的原型
function Person(name) {this.name = name;}

Person.prototype.constructor = Person
  • 在产生 new 结构函数调用时,会将创立的新对象的 [[Prototype]] 链接到 Person.prototype 指向的对象,这个机制就被称为原型链继承
  • 办法定义在原型上,属性定义在构造函数上
  • 首先要说一下 JS 原型和实例的关系:每个构造函数(constructor)都有一个原型对象(prototype),这个原型对象蕴含一个指向此构造函数的指针属性,通过 new 进行结构函数调用生成的实例,此实例蕴含一个指向原型对象的指针,也就是通过 [[Prototype]] 链接到了这个原型对象
  • 而后说一下 JS 中属性的查找:当咱们试图援用实例对象的某个属性时,是依照这样的形式去查找的,首先查找实例对象上是否有这个属性,如果没有找到,就去结构这个实例对象的构造函数的 prototype 所指向的对象下来查找,如果还找不到,就从这个 prototype 对象所指向的构造函数的 prototype 原型对象下来查找
  • 什么是原型链:这样逐级查找形似一个链条,且通过 [[Prototype]] 属性链接,所以被称为原型链
  • 什么是原型链继承,类比类的继承:当有两个构造函数 A 和 B,将一个构造函数 A 的原型对象的,通过其 [[Prototype]] 属性链接到另外一个 B 构造函数的原型对象时,这个过程被称之为原型继承。

标准答案更正确的解释

什么是原型链?

当对象查找一个属性的时候,如果没有在本身找到,那么就会查找本身的原型,如果原型还没有找到,那么会持续查找原型的原型,直到找到 Object.prototype 的原型时,此时原型为 null,查找进行。
这种通过 通过原型链接的逐级向上的查找链被称为原型链

什么是原型继承?

一个对象能够应用另外一个对象的属性或者办法,就称之为继承。具体是通过将这个对象的原型设置为另外一个对象,这样依据原型链的规定,如果查找一个对象属性且在本身不存在时,就会查找另外一个对象,相当于一个对象能够应用另外一个对象的属性和办法了。

抉择排序 – 工夫复杂度 n^2

题目形容: 实现一个抉择排序

实现代码如下:

function selectSort(arr) {
  // 缓存数组长度
  const len = arr.length;
  // 定义 minIndex,缓存以后区间最小值的索引,留神是索引
  let minIndex;
  // i 是以后排序区间的终点
  for (let i = 0; i < len - 1; i++) {
    // 初始化 minIndex 为以后区间第一个元素
    minIndex = i;
    // i、j 别离定义以后区间的上下界,i 是左边界,j 是右边界
    for (let j = i; j < len; j++) {
      // 若 j 处的数据项比以后最小值还要小,则更新最小值索引为 j
      if (arr[j] < arr[minIndex]) {minIndex = j;}
    }
    // 如果 minIndex 对应元素不是目前的头部元素,则替换两者
    if (minIndex !== i) {[arr[i], arr[minIndex]] = [arr[minIndex], arr[i]];
    }
  }
  return arr;
}
// console.log(quickSort([3, 6, 2, 4, 1]));

HTTP 1.1 和 HTTP 2.0 的区别

  • 二进制协定:HTTP/2 是一个二进制协定。在 HTTP/1.1 版中,报文的头信息必须是文本(ASCII 编码),数据体能够是文本,也能够是二进制。HTTP/2 则是一个彻底的二进制协定,头信息和数据体都是二进制,并且统称为 ” 帧 ”,能够分为头信息帧和数据帧。帧的概念是它实现多路复用的根底。
  • 多路复用: HTTP/2 实现了多路复用,HTTP/2 依然复用 TCP 连贯,然而在一个连贯里,客户端和服务器都能够同时发送多个申请或回应,而且不必依照程序一一发送,这样就防止了 ” 队头梗塞 ”【1】的问题。
  • 数据流: HTTP/2 应用了数据流的概念,因为 HTTP/2 的数据包是不按程序发送的,同一个连贯外面间断的数据包,可能属于不同的申请。因而,必须要对数据包做标记,指出它属于哪个申请。HTTP/2 将每个申请或回应的所有数据包,称为一个数据流。每个数据流都有一个举世无双的编号。数据包发送时,都必须标记数据流 ID,用来辨别它属于哪个数据流。
  • 头信息压缩: HTTP/2 实现了头信息压缩,因为 HTTP 1.1 协定不带状态,每次申请都必须附上所有信息。所以,申请的很多字段都是反复的,比方 Cookie 和 User Agent,截然不同的内容,每次申请都必须附带,这会节约很多带宽,也影响速度。HTTP/2 对这一点做了优化,引入了头信息压缩机制。一方面,头信息应用 gzip 或 compress 压缩后再发送;另一方面,客户端和服务器同时保护一张头信息表,所有字段都会存入这个表,生成一个索引号,当前就不发送同样字段了,只发送索引号,这样就能进步速度了。
  • 服务器推送: HTTP/2 容许服务器未经请求,被动向客户端发送资源,这叫做服务器推送。应用服务器推送提前给客户端推送必要的资源,这样就能够绝对缩小一些延迟时间。这里须要留神的是 http2 下服务器被动推送的是动态资源,和 WebSocket 以及应用 SSE 等形式向客户端发送即时数据的推送是不同的。

【1】队头梗塞:

队头阻塞是由 HTTP 根本的“申请 – 应答”模型所导致的。HTTP 规定报文必须是“一发一收”,这就造成了一个先进先出的“串行”队列。队列里的申请是没有优先级的,只有入队的先后顺序,排在最后面的申请会被最优先解决。如果队首的申请因为解决的太慢耽搁了工夫,那么队列里前面的所有申请也不得不跟着一起期待,后果就是其余的申请承当了不应有的工夫老本,造成了队头梗塞的景象。

箭头函数与一般函数的区别

(1)箭头函数比一般函数更加简洁

  • 如果没有参数,就间接写一个空括号即可
  • 如果只有一个参数,能够省去参数的括号
  • 如果有多个参数,用逗号宰割
  • 如果函数体的返回值只有一句,能够省略大括号
  • 如果函数体不须要返回值,且只有一句话,能够给这个语句后面加一个 void 关键字。最常见的就是调用一个函数:
let fn = () => void doesNotReturn();

(2)箭头函数没有本人的 this

箭头函数不会创立本人的 this,所以它没有本人的 this,它只会在本人作用域的上一层继承 this。所以箭头函数中 this 的指向在它在定义时曾经确定了,之后不会扭转。

(3)箭头函数继承来的 this 指向永远不会扭转

var id = 'GLOBAL';
var obj = {
  id: 'OBJ',
  a: function(){console.log(this.id);
  },
  b: () => {console.log(this.id);
  }
};
obj.a();    // 'OBJ'
obj.b();    // 'GLOBAL'
new obj.a()  // undefined
new obj.b()  // Uncaught TypeError: obj.b is not a constructor

对象 obj 的办法 b 是应用箭头函数定义的,这个函数中的 this 就永远指向它定义时所处的全局执行环境中的 this,即使这个函数是作为对象 obj 的办法调用,this 仍旧指向 Window 对象。须要留神,定义对象的大括号 {} 是无奈造成一个独自的执行环境的,它仍旧是处于全局执行环境中。

(4)call()、apply()、bind()等办法不能扭转箭头函数中 this 的指向

var id = 'Global';
let fun1 = () => {console.log(this.id)
};
fun1();                     // 'Global'
fun1.call({id: 'Obj'});     // 'Global'
fun1.apply({id: 'Obj'});    // 'Global'
fun1.bind({id: 'Obj'})();   // 'Global'

(5)箭头函数不能作为构造函数应用

构造函数在 new 的步骤在下面曾经说过了,实际上第二步就是将函数中的 this 指向该对象。然而因为箭头函数时没有本人的 this 的,且 this 指向外层的执行环境,且不能扭转指向,所以不能当做构造函数应用。

(6)箭头函数没有本人的 arguments

箭头函数没有本人的 arguments 对象。在箭头函数中拜访 arguments 实际上取得的是它外层函数的 arguments 值。

(7)箭头函数没有 prototype

(8)箭头函数不能用作 Generator 函数,不能应用 yeild 关键字

代码输入问题

function A(){}
function B(a){this.a = a;}
function C(a){if(a){this.a = a;}
}
A.prototype.a = 1;
B.prototype.a = 1;
C.prototype.a = 1;

console.log(new A().a);
console.log(new B().a);
console.log(new C(2).a);

输入后果:1 undefined 2

解析:

  1. console.log(new A().a),new A()为构造函数创立的对象,自身没有 a 属性,所以向它的原型去找,发现原型的 a 属性的属性值为 1,故该输入值为 1;
  2. console.log(new B().a),ew B()为构造函数创立的对象,该构造函数有参数 a,但该对象没有传参,故该输入值为 undefined;
  3. console.log(new C(2).a),new C()为构造函数创立的对象,该构造函数有参数 a,且传的实参为 2,执行函数外部,发现 if 为真,执行 this.a = 2, 故属性 a 的值为 2。

冒泡排序 – 工夫复杂度 n^2

题目形容: 实现一个冒泡排序

实现代码如下:

function bubbleSort(arr) {
  // 缓存数组长度
  const len = arr.length;
  // 外层循环用于管制从头到尾的比拟 + 替换到底有多少轮
  for (let i = 0; i < len; i++) {
    // 内层循环用于实现每一轮遍历过程中的反复比拟 + 替换
    for (let j = 0; j < len - 1; j++) {
      // 若相邻元素后面的数比前面的大
      if (arr[j] > arr[j + 1]) {
        // 替换两者
        [arr[j], arr[j + 1]] = [arr[j + 1], arr[j]];
      }
    }
  }
  // 返回数组
  return arr;
}
// console.log(bubbleSort([3, 6, 2, 4, 1]));

new 一个构造函数,如果函数返回 return {}return nullreturn 1return true 会产生什么状况?

如果函数返回一个对象,那么 new 这个函数调用返回这个函数的返回对象,否则返回 new 创立的新对象

对作用域、作用域链的了解

1)全局作用域和函数作用域

(1)全局作用域

  • 最外层函数和最外层函数里面定义的变量领有全局作用域
  • 所有未定义间接赋值的变量主动申明为全局作用域
  • 所有 window 对象的属性领有全局作用域
  • 全局作用域有很大的弊病,过多的全局作用域变量会净化全局命名空间,容易引起命名抵触。

(2)函数作用域

  • 函数作用域申明在函数外部的变零,个别只有固定的代码片段能够拜访到
  • 作用域是分层的,内层作用域能够拜访外层作用域,反之不行
2)块级作用域
  • 应用 ES6 中新增的 let 和 const 指令能够申明块级作用域,块级作用域能够在函数中创立也能够在一个代码块中的创立(由 {} 包裹的代码片段)
  • let 和 const 申明的变量不会有变量晋升,也不能够反复申明
  • 在循环中比拟适宜绑定块级作用域,这样就能够把申明的计数器变量限度在循环外部。

作用域链: 在以后作用域中查找所需变量,然而该作用域没有这个变量,那这个变量就是自在变量。如果在本人作用域找不到该变量就去父级作用域查找,顺次向下级作用域查找,直到拜访到 window 对象就被终止,这一层层的关系就是作用域链。

作用域链的作用是 保障对执行环境有权拜访的所有变量和函数的有序拜访,通过作用域链,能够拜访到外层环境的变量和函数。

作用域链的实质上是一个指向变量对象的指针列表。变量对象是一个蕴含了执行环境中所有变量和函数的对象。作用域链的前端始终都是以后执行上下文的变量对象。全局执行上下文的变量对象(也就是全局对象)始终是作用域链的最初一个对象。

当查找一个变量时,如果以后执行环境中没有找到,能够沿着作用域链向后查找。

DNS 同时应用 TCP 和 UDP 协定?

DNS 占用 53 号端口,同时应用 TCP 和 UDP 协定。(1)在区域传输的时候应用 TCP 协定

  • 辅域名服务器会定时(个别 3 小时)向主域名服务器进行查问以便理解数据是否有变动。如有变动,会执行一次区域传送,进行数据同步。区域传送应用 TCP 而不是 UDP,因为数据同步传送的数据量比一个申请应答的数据量要多得多。
  • TCP 是一种牢靠连贯,保障了数据的准确性。

(2)在域名解析的时候应用 UDP 协定

  • 客户端向 DNS 服务器查问域名,个别返回的内容都不超过 512 字节,用 UDP 传输即可。不必通过三次握手,这样 DNS 服务器负载更低,响应更快。实践上说,客户端也能够指定向 DNS 服务器查问时用 TCP,但事实上,很多 DNS 服务器进行配置的时候,仅反对 UDP 查问包。

箭头函数和一般函数有啥区别?箭头函数能当构造函数吗?

  • 一般函数通过 function 关键字定义,this 无奈联合词法作用域应用,在运行时绑定,只取决于函数的调用形式,在哪里被调用,调用地位。(取决于调用者,和是否独立运行)
  • 箭头函数应用被称为“胖箭头”的操作 => 定义,箭头函数不利用一般函数 this 绑定的四种规定,而是依据外层(函数或全局)的作用域来决定 this,且箭头函数的绑定无奈被批改(new 也不行)。

    • 箭头函数罕用于回调函数中,包含事件处理器或定时器
    • 箭头函数和 var self = this,都试图取代传统的 this 运行机制,将 this 的绑定拉回到词法作用域
    • 没有原型、没有 this、没有 super,没有 arguments,没有 new.target
    • 不能通过 new 关键字调用

      • 一个函数外部有两个办法:[[Call]] 和 [[Construct]],在通过 new 进行函数调用时,会执行 [[construct]] 办法,创立一个实例对象,而后再执行这个函数体,将函数的 this 绑定在这个实例对象上
      • 当间接调用时,执行 [[Call]] 办法,间接执行函数体
      • 箭头函数没有 [[Construct]] 办法,不能被用作结构函数调用,当应用 new 进行函数调用时会报错。
function foo() {return (a) => {console.log(this.a);
  }
}

var obj1 = {a: 2}

var obj2 = {a: 3}

var bar = foo.call(obj1);
bar.call(obj2);

过程和线程的区别

  • 过程能够看做独立利用,线程不能
  • 资源:过程是 cpu 资源分配的最小单位(是能领有资源和独立运行的最小单位);线程是 cpu 调度的最小单位(线程是建设在过程的根底上的一次程序运行单位,一个过程中能够有多个线程)。
  • 通信方面:线程间能够通过间接共享同一过程中的资源,而过程通信须要借助 过程间通信。
  • 调度:过程切换比线程切换的开销要大。线程是 CPU 调度的根本单位,线程的切换不会引起过程切换,但某个过程中的线程切换到另一个过程中的线程时,会引起过程切换。
  • 零碎开销:因为创立或撤销过程时,零碎都要为之调配或回收资源,如内存、I/O 等,其开销远大于创立或撤销线程时的开销。同理,在进行过程切换时,波及以后执行过程 CPU 环境还有各种各样状态的保留及新调度过程状态的设置,而线程切换时只需保留和设置大量寄存器内容,开销较小。

渲染过程中遇到 JS 文件如何解决?

JavaScript 的加载、解析与执行会阻塞文档的解析,也就是说,在构建 DOM 时,HTML 解析器若遇到了 JavaScript,那么它会暂停文档的解析,将控制权移交给 JavaScript 引擎,等 JavaScript 引擎运行结束,浏览器再从中断的中央复原持续解析文档。也就是说,如果想要首屏渲染的越快,就越不应该在首屏就加载 JS 文件,这也是都倡议将 script 标签放在 body 标签底部的起因。当然在当下,并不是说 script 标签必须放在底部,因为你能够给 script 标签增加 defer 或者 async 属性。

列表转成树形构造

题目形容:

[
    {
        id: 1,
        text: '节点 1',
        parentId: 0 // 这里用 0 示意为顶级节点
    },
    {
        id: 2,
        text: '节点 1_1',
        parentId: 1 // 通过这个字段来确定子父级
    }
    ...
]

转成
[
    {
        id: 1,
        text: '节点 1',
        parentId: 0,
        children: [
            {
                id:2,
                text: '节点 1_1',
                parentId:1
            }
        ]
    }
]

实现代码如下:

function listToTree(data) {let temp = {};
  let treeData = [];
  for (let i = 0; i < data.length; i++) {temp[data[i].id] = data[i];
  }
  for (let i in temp) {if (+temp[i].parentId != 0) {if (!temp[temp[i].parentId].children) {temp[temp[i].parentId].children = [];}
      temp[temp[i].parentId].children.push(temp[i]);
    } else {treeData.push(temp[i]);
    }
  }
  return treeData;
}

对 Service Worker 的了解

Service Worker 是运行在浏览器背地的 独立线程,个别能够用来实现缓存性能。应用 Service Worker 的话,传输协定必须为 HTTPS。因为 Service Worker 中波及到申请拦挡,所以必须应用 HTTPS 协定来保障平安。

Service Worker 实现缓存性能个别分为三个步骤:首先须要先注册 Service Worker,而后监听到 install 事件当前就能够缓存须要的文件,那么在下次用户拜访的时候就能够通过拦挡申请的形式查问是否存在缓存,存在缓存的话就能够间接读取缓存文件,否则就去申请数据。以下是这个步骤的实现:

// index.js
if (navigator.serviceWorker) {
  navigator.serviceWorker
    .register('sw.js')
    .then(function(registration) {console.log('service worker 注册胜利')
    })
    .catch(function(err) {console.log('servcie worker 注册失败')
    })
}
// sw.js
// 监听 `install` 事件,回调中缓存所需文件
self.addEventListener('install', e => {
  e.waitUntil(caches.open('my-cache').then(function(cache) {return cache.addAll(['./index.html', './index.js'])
    })
  )
})
// 拦挡所有申请事件
// 如果缓存中曾经有申请的数据就间接用缓存,否则去申请数据
self.addEventListener('fetch', e => {
  e.respondWith(caches.match(e.request).then(function(response) {if (response) {return response}
      console.log('fetch source')
    })
  )
})

关上页面,能够在开发者工具中的 Application 看到 Service Worker 曾经启动了:在 Cache 中也能够发现所需的文件已被缓存:

Vue 路由守卫有哪些,怎么设置,应用场景等

罕用的两个路由守卫:router.beforeEach 和 router.afterEach

每个守卫办法接管三个参数:to: Route: 行将要进入的指标 路由对象

from: Route: 以后导航正要来到的路由

next: Function: 肯定要调用该办法来 resolve 这个钩子。在我的项目中,个别在 beforeEach 这个钩子函数中进行路由跳转的一些信息判断。判断是否登录,是否拿到对应的路由权限等等。
正文完
 0