关于前端:腾讯前端一面必会面试题合集

21次阅读

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

对对象与数组的解构的了解

解构是 ES6 提供的一种新的提取数据的模式,这种模式可能从对象或数组里有针对性地拿到想要的数值。1)数组的解构 在解构数组时,以元素的地位为匹配条件来提取想要的数据的:

const [a, b, c] = [1, 2, 3]

最终,a、b、c 别离被赋予了数组第 0、1、2 个索引位的值:

数组里的 0、1、2 索引位的元素值,精准地被映射到了左侧的第 0、1、2 个变量里去,这就是数组解构的工作模式。还能够通过给左侧变量数组设置空占位的形式,实现对数组中某几个元素的精准提取:

const [a,,c] = [1,2,3]

通过把两头位留空,能够顺利地把数组第一位和最初一位的值赋给 a、c 两个变量:

2)对象的解构 对象解构比数组构造略微简单一些,也更显弱小。在解构对象时,是以属性的名称为匹配条件,来提取想要的数据的。当初定义一个对象:

const stu = {
  name: 'Bob',
  age: 24
}

如果想要解构它的两个自有属性,能够这样:

const {name, age} = stu

这样就失去了 name 和 age 两个和 stu 平级的变量:

留神,对象解构严格以属性名作为定位根据,所以就算调换了 name 和 age 的地位,后果也是一样的:

const {age, name} = stu

代码输入后果

function a() {console.log(this);
}
a.call(null);

打印后果:window 对象

依据 ECMAScript262 标准规定:如果第一个参数传入的对象调用者是 null 或者 undefined,call 办法将把全局对象(浏览器上是 window 对象)作为 this 的值。所以,不论传入 null 还是 undefined,其 this 都是全局对象 window。所以,在浏览器上答案是输入 window 对象。

要留神的是,在严格模式中,null 就是 null,undefined 就是 undefined:

'use strict';

function a() {console.log(this);
}
a.call(null); // null
a.call(undefined); // undefined

什么是文档的预解析?

Webkit 和 Firefox 都做了这个优化,当执行 JavaScript 脚本时,另一个线程解析剩下的文档,并加载前面须要通过网络加载的资源。这种形式能够使资源并行加载从而使整体速度更快。须要留神的是,预解析并不扭转 DOM 树,它将这个工作留给主解析过程,本人只解析内部资源的援用,比方内部脚本、样式表及图片。

点击刷新按钮或者按 F5、按 Ctrl+F5(强制刷新)、地址栏回车有什么区别?

  • 点击刷新按钮或者按 F5: 浏览器间接对本地的缓存文件过期,然而会带上 If-Modifed-Since,If-None-Match,这就意味着服务器会对文件查看新鲜度,返回后果可能是 304,也有可能是 200。
  • 用户按 Ctrl+F5(强制刷新): 浏览器不仅会对本地文件过期,而且不会带上 If-Modifed-Since,If-None-Match,相当于之前素来没有申请过,返回后果是 200。
  • 地址栏回车:浏览器发动申请,依照失常流程,本地查看是否过期,而后服务器查看新鲜度,最初返回内容。

回流与重绘的概念及触发条件

(1)回流

当渲染树中局部或者全副元素的尺寸、构造或者属性发生变化时,浏览器会从新渲染局部或者全副文档的过程就称为 回流

上面这些操作会导致回流:

  • 页面的首次渲染
  • 浏览器的窗口大小发生变化
  • 元素的内容发生变化
  • 元素的尺寸或者地位发生变化
  • 元素的字体大小发生变化
  • 激活 CSS 伪类
  • 查问某些属性或者调用某些办法
  • 增加或者删除可见的 DOM 元素

在触发回流(重排)的时候,因为浏览器渲染页面是基于流式布局的,所以当触发回流时,会导致四周的 DOM 元素重新排列,它的影响范畴有两种:

  • 全局范畴:从根节点开始,对整个渲染树进行从新布局
  • 部分范畴:对渲染树的某局部或者一个渲染对象进行从新布局

(2)重绘

当页面中某些元素的款式发生变化,然而不会影响其在文档流中的地位时,浏览器就会对元素进行从新绘制,这个过程就是 重绘

上面这些操作会导致回流:

  • color、background 相干属性:background-color、background-image 等
  • outline 相干属性:outline-color、outline-width、text-decoration
  • border-radius、visibility、box-shadow

留神:当触发回流时,肯定会触发重绘,然而重绘不肯定会引发回流。

事件总线(公布订阅模式)

class EventEmitter {constructor() {this.cache = {}
    }
    on(name, fn) {if (this.cache[name]) {this.cache[name].push(fn)
        } else {this.cache[name] = [fn]
        }
    }
    off(name, fn) {let tasks = this.cache[name]
        if (tasks) {const index = tasks.findIndex(f => f === fn || f.callback === fn)
            if (index >= 0) {tasks.splice(index, 1)
            }
        }
    }
    emit(name, once = false, ...args) {if (this.cache[name]) {
            // 创立正本,如果回调函数内持续注册雷同事件,会造成死循环
            let tasks = this.cache[name].slice()
            for (let fn of tasks) {fn(...args)
            }
            if (once) {delete this.cache[name]
            }
        }
    }
}

// 测试
let eventBus = new EventEmitter()
let fn1 = function(name, age) {console.log(`${name} ${age}`)
}
let fn2 = function(name, age) {console.log(`hello, ${name} ${age}`)
}
eventBus.on('aaa', fn1)
eventBus.on('aaa', fn2)
eventBus.emit('aaa', false, '布兰', 12)
// '布兰 12'
// 'hello, 布兰 12'

事件是什么?事件模型?

事件是用户操作网页时产生的交互动作,比方 click/move,事件除了用户触发的动作外,还能够是文档加载,窗口滚动和大小调整。事件被封装成一个 event 对象,蕴含了该事件产生时的所有相干信息(event 的属性)以及能够对事件进行的操作(event 的办法)。

事件是用户操作网页时产生的交互动作或者网页自身的一些操作,古代浏览器一共有三种事件模型:

  • DOM0 级事件模型,这种模型不会流传,所以没有事件流的概念,然而当初有的浏览器反对以冒泡的形式实现,它能够在网页中间接定义监听函数,也能够通过 js 属性来指定监听函数。所有浏览器都兼容这种形式。间接在 dom 对象上注册事件名称,就是 DOM0 写法。
  • IE 事件模型,在该事件模型中,一次事件共有两个过程,事件处理阶段和事件冒泡阶段。事件处理阶段会首先执行指标元素绑定的监听事件。而后是事件冒泡阶段,冒泡指的是事件从指标元素冒泡到 document,顺次查看通过的节点是否绑定了事件监听函数,如果有则执行。这种模型通过 attachEvent 来增加监听函数,能够增加多个监听函数,会按程序顺次执行。
  • DOM2 级事件模型,在该事件模型中,一次事件共有三个过程,第一个过程是事件捕捉阶段。捕捉指的是事件从 document 始终向下流传到指标元素,顺次查看通过的节点是否绑定了事件监听函数,如果有则执行。前面两个阶段和 IE 事件模型的两个阶段雷同。这种事件模型,事件绑定的函数是 addEventListener,其中第三个参数能够指定事件是否在捕捉阶段执行。

过程之前的通信形式

(1)管道通信

管道是一种最根本的过程间通信机制。管道就是操作系统在内核中开拓的一段缓冲区,过程 1 能够将须要交互的数据拷贝到这段缓冲区,过程 2 就能够读取了。

管道的特点:

  • 只能单向通信
  • 只能血缘关系的过程进行通信
  • 依赖于文件系统
  • 生命周期随过程
  • 面向字节流的服务
  • 管道外部提供了同步机制

(2)音讯队列通信

音讯队列就是一个音讯的列表。用户能够在音讯队列中增加音讯、读取音讯等。音讯队列提供了一种从一个过程向另一个过程发送一个数据块的办法。每个数据块都被认为含有一个类型,接管过程能够独立地接管含有不同类型的数据结构。能够通过发送音讯来防止命名管道的同步和阻塞问题。然而音讯队列与命名管道一样,每个数据块都有一个最大长度的限度。

应用音讯队列进行过程间通信,可能会收到数据块最大长度的限度束缚等,这也是这种通信形式的毛病。如果频繁的产生过程间的通信行为,那么过程须要频繁地读取队列中的数据到内存,相当于间接地从一个过程拷贝到另一个过程,这须要破费工夫。

(3)信号量通信

共享内存最大的问题就是多过程竞争内存的问题,就像相似于线程平安问题。咱们能够应用信号量来解决这个问题。信号量的实质就是一个计数器,用来实现过程之间的互斥与同步。例如信号量的初始值是 1,而后 a 过程来拜访内存 1 的时候,咱们就把信号量的值设为 0,而后过程 b 也要来拜访内存 1 的时候,看到信号量的值为 0 就晓得曾经有过程在拜访内存 1 了,这个时候过程 b 就会拜访不了内存 1。所以说,信号量也是过程之间的一种通信形式。

(4)信号通信

信号(Signals)是 Unix 零碎中应用的最古老的过程间通信的办法之一。操作系统通过信号来告诉过程零碎中产生了某种预先规定好的事件(一组事件中的一个),它也是用户过程之间通信和同步的一种原始机制。

(5)共享内存通信

共享内存就是映射一段能被其余过程所拜访的内存,这段共享内存由一个过程创立,但多个过程都能够拜访(使多个过程能够拜访同一块内存空间)。共享内存是最快的 IPC 形式,它是针对其余过程间通信形式运行效率低而专门设计的。它往往与其余通信机制,如信号量,配合应用,来实现过程间的同步和通信。

(6)套接字通信

下面说的共享内存、管道、信号量、音讯队列,他们都是多个过程在一台主机之间的通信,那两个相隔几千里的过程可能进行通信吗?答是必须的,这个时候 Socket 这家伙就派上用场了,例如咱们平时通过浏览器发动一个 http 申请,而后服务器给你返回对应的数据,这种就是采纳 Socket 的通信形式了。

await 到底在等啥?

await 在期待什么呢? 一般来说,都认为 await 是在期待一个 async 函数实现。不过按语法阐明,await 期待的是一个表达式,这个表达式的计算结果是 Promise 对象或者其它值(换句话说,就是没有非凡限定)。

因为 async 函数返回一个 Promise 对象,所以 await 能够用于期待一个 async 函数的返回值——这也能够说是 await 在等 async 函数,但要分明,它等的理论是一个返回值。留神到 await 不仅仅用于等 Promise 对象,它能够等任意表达式的后果,所以,await 前面理论是能够接一般函数调用或者间接量的。所以上面这个示例齐全能够正确运行:

function getSomething() {return "something";}
async function testAsync() {return Promise.resolve("hello async");
}
async function test() {const v1 = await getSomething();
    const v2 = await testAsync();
    console.log(v1, v2);
}
test();

await 表达式的运算后果取决于它等的是什么。

  • 如果它等到的不是一个 Promise 对象,那 await 表达式的运算后果就是它等到的货色。
  • 如果它等到的是一个 Promise 对象,await 就忙起来了,它会阻塞前面的代码,等着 Promise 对象 resolve,而后失去 resolve 的值,作为 await 表达式的运算后果。

来看一个例子:

function testAsy(x){return new Promise(resolve=>{setTimeout(() => {resolve(x);
     }, 3000)
    }
   )
}
async function testAwt(){let result =  await testAsy('hello world');
  console.log(result);    // 3 秒钟之后呈现 hello world
  console.log('cuger')   // 3 秒钟之后呈现 cug
}
testAwt();
console.log('cug')  // 立刻输入 cug

这就是 await 必须用在 async 函数中的起因。async 函数调用不会造成阻塞,它外部所有的阻塞都被封装在一个 Promise 对象中异步执行。await 暂停以后 async 的执行,所以 ’cug” 最先输入,hello world’ 和‘cuger’是 3 秒钟后同时呈现的。

寄生组合继承

题目形容: 实现一个你认为不错的 js 继承形式

实现代码如下:

function Parent(name) {
  this.name = name;
  this.say = () => {console.log(111);
  };
}
Parent.prototype.play = () => {console.log(222);
};
function Children(name) {Parent.call(this);
  this.name = name;
}
Children.prototype = Object.create(Parent.prototype);
Children.prototype.constructor = Children;
// let child = new Children("111");
// // console.log(child.name);
// // child.say();
// // child.play();

intanceof 操作符的实现原理及实现

instanceof 运算符用于判断构造函数的 prototype 属性是否呈现在对象的原型链中的任何地位。

function myInstanceof(left, right) {
  // 获取对象的原型
  let proto = Object.getPrototypeOf(left)
  // 获取构造函数的 prototype 对象
  let prototype = right.prototype; 

  // 判断构造函数的 prototype 对象是否在对象的原型链上
  while (true) {if (!proto) return false;
    if (proto === prototype) return true;
    // 如果没有找到,就持续从其原型上找,Object.getPrototypeOf 办法用来获取指定对象的原型
    proto = Object.getPrototypeOf(proto);
  }
}

JS 整数是怎么示意的?

  • 通过 Number 类型来示意,遵循 IEEE754 规范,通过 64 位来示意一个数字,(1 + 11 + 52),最大平安数字是 Math.pow(2, 53) – 1,对于 16 位十进制。(符号位 + 指数位 + 小数局部无效位)

map 和 Object 的区别

Map Object
意外的键 Map 默认状况不蕴含任何键,只蕴含显式插入的键。 Object 有一个原型, 原型链上的键名有可能和本人在对象上的设置的键名产生抵触。
键的类型 Map 的键能够是任意值,包含函数、对象或任意根本类型。 Object 的键必须是 String 或是 Symbol。
键的程序 Map 中的 key 是有序的。因而,当迭代的时候,Map 对象以插入的程序返回键值。 Object 的键是无序的
Size Map 的键值对个数能够轻易地通过 size 属性获取 Object 的键值对个数只能手动计算
迭代 Map 是 iterable 的,所以能够间接被迭代。 迭代 Object 须要以某种形式获取它的键而后能力迭代。
性能 在频繁增删键值对的场景下体现更好。 在频繁增加和删除键值对的场景下未作出优化。

margin 和 padding 的应用场景

  • 须要在 border 外侧增加空白,且空白处不须要背景(色)时,应用 margin;
  • 须要在 border 内测增加空白,且空白处须要背景(色)时,应用 padding。

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

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

说一下类组件和函数组件的区别?

1. 语法上的区别:函数式组件是一个纯函数,它是须要承受 props 参数并且返回一个 React 元素就能够了。类组件是须要继承 React.Component 的,而且 class 组件须要创立 render 并且返回 React 元素,语法上来讲更简单。2. 调用形式

函数式组件能够间接调用,返回一个新的 React 元素;类组件在调用时是须要创立一个实例的,而后通过调用实例里的 render 办法来返回一个 React 元素。3. 状态治理

函数式组件没有状态治理,类组件有状态治理。4. 应用场景

类组件没有具体的要求。函数式组件个别是用在大型项目中来宰割大组件(函数式组件不必创立实例,所有更高效),个别状况下能用函数式组件就不必类组件,晋升效率。

写版本号排序的办法

题目形容: 有一组版本号如下[‘0.1.1’, ‘2.3.3’, ‘0.302.1’, ‘4.2’, ‘4.3.5’, ‘4.3.4.5’]。当初须要对其进行排序,排序的后果为 [‘4.3.5′,’4.3.4.5′,’2.3.3′,’0.302.1′,’0.1.1’]

实现代码如下:

arr.sort((a, b) => {
  let i = 0;
  const arr1 = a.split(".");
  const arr2 = b.split(".");

  while (true) {const s1 = arr1[i];
    const s2 = arr2[i];
    i++;
    if (s1 === undefined || s2 === undefined) {return arr2.length - arr1.length;}

    if (s1 === s2) continue;

    return s2 - s1;
  }
});
console.log(arr);

如何判断一个对象是否属于某个类?

  • 第一种形式,应用 instanceof 运算符来判断构造函数的 prototype 属性是否呈现在对象的原型链中的任何地位。
  • 第二种形式,通过对象的 constructor 属性来判断,对象的 constructor 属性指向该对象的构造函数,然而这种形式不是很平安,因为 constructor 属性能够被改写。
  • 第三种形式,如果须要判断的是某个内置的援用类型的话,能够应用 Object.prototype.toString() 办法来打印对象的[[Class]] 属性来进行判断。

正文完
 0