zl程序教程

您现在的位置是:首页 >  前端

当前栏目

56 道高频 JavaScript 与 ES6+ 的面试题及答案(上)

JavaScript面试题ES6 答案 高频 56
2023-09-27 14:25:58 时间
前言

本文讲解 56 道 JavaScript 和 ES6 面试题的内容。


复习前端面试的知识 是为了巩固前端的基础知识 最重要的还是平时的积累

注意 文章的题与题之间用下划线分隔开 答案仅供参考。


前端硬核面试专题的完整版在此 前端硬核面试专题 包含 HTML CSS JS ES6 Webpack Vue React Node HTTPS 数据结构与算法 Git 。


JavaScript

常见的浏览器内核有哪些


Trident 内核 IE, 360 搜狗浏览器 MaxThon、TT、The World,等。[又称 MSHTML]Gecko 内核 火狐 FF MozillaSuite / SeaMonkey 等Presto 内核 Opera7 及以上。[Opera 内核原为 Presto 现为 Blink]Webkit 内核 Safari Chrome 等。 [ Chrome 的 Blink WebKit 的分支 ]

mouseenter 和 mouseover 的区别


不论鼠标指针穿过被选元素或其子元素 都会触发 mouseover 事件 对应 mouseout。只有在鼠标指针穿过被选元素时 才会触发 mouseenter 事件 对应 mouseleave。

用正则表达式匹配字符串 以字母开头 后面是数字、字符串或者下划线 长度为 9 - 20


var re new RegExp( ^[a-zA-Z][a-zA-Z0-9_]{9,20}$ 

手机号码校验


function checkPhone(){ 

 var phone document.getElementById( phone ).value;

 if(!(/^1(3|4|5|7|8)\d{9}$/.test(phone))){ 

 alert( 手机号码有误 请重填 

 return false; 

^1(3|4|5|7|8)d{9}$ 表示以 1 开头 第二位可能是 3/4/5/7/8 等的任意一个 在加上后面的 d 表示数字 [0-9] 的 9 位 总共加起来 11 位结束。

手机号码格式验证方法(正则表达式验证)支持最新电信 199 移动 198 联通 166

// 手机号码校验规则

let valid_rule /^(13[0-9]|14[5-9]|15[012356789]|166|17[0-8]|18[0-9]|19[8-9])[0-9]{8}$/;

if ( ! valid_rule.test(phone_number)) {

 alert( 手机号码格式有误 

 return false;

}


这样 phone_number 就是取到的手机号码 即可

js 字符串两边截取空白的 trim 的原型方法的实现


js 中本身是没有 trim 函数的。


// 删除左右两端的空格

function trim(str){

return str.replace(/(^\s*)|(\s*$)/g, 

// 删除左边的空格 /(^\s*)/g

// 删除右边的空格 /(\s*$)/g

介绍一下你对浏览器内核的理解 ?


内核主要分成两部分 渲染引擎(layout engineer 或 Rendering Engine) 和 JS 引擎。

渲染引擎


负责取得网页的内容 HTML、XML、图像等等 、整理讯息 例如加入 CSS 等 以及计算网页的显示方式 然后会输出至显示器或打印机。


浏览器的内核的不同对于网页的语法解释会有不同 所以渲染的效果也不相同。

所有网页浏览器、电子邮件客户端以及其它需要编辑、显示网络内容的应用程序都需要内核。


JS 引擎


解析和执行 javascript 来实现网页的动态效果。


最开始渲染引擎和 JS 引擎并没有区分的很明确 后来 JS 引擎越来越独立 内核就倾向于只指渲染引擎。


哪些常见操作会造成内存泄漏


内存泄漏指任何对象在您不再拥有或需要它之后仍然存在。


垃圾回收器定期扫描对象 并计算引用了每个对象的其他对象的数量。如果一个对象的引用数量为 0 没有其他对象引用过该对象 或对该对象的惟一引用是循环的 那么该对象的内存即可回收。


setTimeout 的第一个参数使用字符串而非函数的话 会引发内存泄漏。闭包、控制台日志、循环 在两个对象彼此引用且彼此保留时 就会产生一个循环 。

线程与进程的区别


一个程序至少有一个进程 一个进程至少有一个线程。线程的划分尺度小于进程 使得多线程程序的并发性高。另外 进程在执行过程中拥有独立的内存单元 而多个线程共享内存 从而极大地提高了程序的运行效率。

线程在执行过程中与进程还是有区别的。

每个独立的线程有一个程序运行的入口、顺序执行序列和程序的出口。但是线程不能够独立执行 必须依存在应用程序中 由应用程序提供多个线程执行控制。从逻辑角度来看 多线程的意义在于一个应用程序中 有多个执行部分可以同时执行。


但操作系统并没有将多个线程看做多个独立的应用 来实现进程的调度和管理以及资源分配。这就是进程和线程的重要区别。


eval() 函数有什么用


eval() 函数可计算某个字符串 并执行其中的的 JavaScript 代码。

实现一个方法 使得 add(2, 5) 和 add(2)(5) 的结果都为 7


var add function (x, r) {

 if (arguments.length 1) {

 return function (y) { return x };

 } else {

 return x 

console.log(add(2)(5)); // 7

console.log(add(2,5)); // 7

alert(1 2) 和 alert(1 || 0) 的结果是


alert(1 2 ) 的结果是 2


只要 “ ” 前面是 false 无论 “ ” 后面是 true 还是 false 结果都将返 “ ” 前面的值;只要 “ ” 前面是 true 无论 “ ” 后面是 true 还是 false 结果都将返 “ ” 后面的值;


alert(0 || 1) 的结果是 1


只要 “||” 前面为 false 不管 “||” 后面是 true 还是 false 都返回 “||” 后面的值。只要 “||” 前面为 true 不管 “||” 后面是 true 还是 false 都返回 “||” 前面的值。


只要记住 0 与 任何数都是 0 其他反推。

下面的输出结果是


var out 25,

 inner {

 out: 20,

 func: function () {

 var out 30;

 return this.out;

console.log((inner.func, inner.func)());

console.log(inner.func());

console.log((inner.func)());

console.log((inner.func inner.func)());


结果 25 20 20 25


代码解析 这道题的考点分两个


作用域运算符 赋值预算 逗号运算


先看第一个输出 25 因为 ( inner.func, inner.func ) 是进行逗号运算符 逗号运算符就是运算前面的 ”,“ 返回最后一个 举个栗子


var i 0, j 1, k 

console.log((i , j , k)) // 返回的是 k 的值 2 如果写成 k 的话 这里返回的就是 3

console.log(i); // 1

console.log(j); // 2

console.log(k); // 2


回到原题 ( inner.func, inner.func ) 就是返回 inner.func 而 inner.func 只是一个匿名函数


function () {

 var out 30;

 return this.out;

}


而且这个匿名函数是属于 window 的 则变成了


(function () {

 var out 30;

 return this.out;

})()


此刻的 this window


所以 out 是 25。


第二和第三个 console.log 的作用域都是 inner 也就是他们执行的其实是 inner.func();

inner 作用域中是有 out 变量的 所以结果是 20。


第四个 console.log 考查的是一个等号运算 inner.func inner.func 其实返回的是运算的结果


举个栗子


var a 2, b 

console.log(a b) // 输出的是 3


所以 inner.func inner.func 返回的也是一个匿名函数


function () {

 var out 30;

 return this.out;

}


此刻 道理就和第一个 console.log 一样了 输出的结果是 25。


下面程序输出的结果是


if (!( a in window)) {

 var a 

alert(a);


代码解析 如果 window 不包含属性 a 就声明一个变量 a 然后赋值为 1。

你可能认为 alert 出来的结果是 1 然后实际结果是 “undefined”。


要了解为什么 需要知道 JavaScript 里的 3 个概念。


首先 所有的全局变量都是 window 的属性 语句 var a 等价于 window.a

你可以用如下方式来检测全局变量是否声明 变量名称 in window。


第二 所有的变量声明都在范围作用域的顶部 看一下相似的例子


alert( b in window);

var b;


此时 尽管声明是在 alert 之后 alert 弹出的依然是 true 这是因为 JavaScript 引擎首先会扫描所有的变量声明 然后将这些变量声明移动到顶部 最终的代码效果是这样的


var a;

alert( a in window);


这样看起来就很容易解释为什么 alert 结果是 true 了。


第三 你需要理解该题目的意思是 变量声明被提前了 但变量赋值没有 因为这行代码包括了变量声明和变量赋值。


你可以将语句拆分为如下代码


var a; //声明

a //初始化赋值


当变量声明和赋值在一起用的时候 JavaScript 引擎会自动将它分为两部以便将变量声明提前

不将赋值的步骤提前 是因为他有可能影响代码执行出不可预期的结果。


所以 知道了这些概念以后 重新回头看一下题目的代码 其实就等价于


var a;

if (!( a in window)) {

alert(a);


这样 题目的意思就非常清楚了 首先声明 a 然后判断 a 是否在存在 如果不存在就赋值为1 很明显 a 永远在 window 里存在 这个赋值语句永远不会执行 所以结果是 undefined。


提前这个词语显得有点迷惑了 你可以理解为 预编译。

下面程序输出的结果是


var a 

var b function a(x) {

 x a(--x);

alert(a);


这个题目看起来比实际复杂 alert 的结果是 1。


这里依然有 3 个重要的概念需要我们知道。


首先 第一个是 变量声明在进入执行上下文就完成了 第二个概念就是函数声明也是提前的 所有的函数声明都在执行代码之前都已经完成了声明 和变量声明一样。


澄清一下 函数声明是如下这样的代码


function functionName(arg1, arg2){

 //函数体

如下不是函数 而是函数表达式 相当于变量赋值 

var functionName function(arg1, arg2){

 //函数体

 };


澄清一下 函数表达式没有提前 就相当于平时的变量赋值。


第三需要知道的是 函数声明会覆盖变量声明 但不会覆盖变量赋值。

为了解释这个 我们来看一个例子


function value(){

 return 1;

var value;

alert(typeof value); // function 


尽管变量声明在下面定义 但是变量 value 依然是 function 也就是说这种情况下 函数声明的优先级高于变量声明的优先级 但如果该变量 value 赋值了 那结果就完全不一样了


function value(){

 return 1;

var value 

alert(typeof value); // number 


该 value 赋值以后 变量赋值初始化就覆盖了函数声明。


重新回到题目 这个函数其实是一个有名函数表达式 函数表达式不像函数声明一样可以覆盖变量声明 但你可以注意到 变量 b 是包含了该函数表达式 而该函数表达式的名字是 a。不同的浏览器对 a 这个名词处理有点不一样 在 IE 里 会将 a 认为函数声明 所以它被变量初始化覆盖了 就是说如果调用 a(–x) 的话就会出错 而其它浏览器在允许在函数内部调用 a(–x) 因为这时候 a 在函数外面依然是数字。


基本上 IE 里调用 b(2) 的时候会出错 但其它浏览器则返回 undefined。


理解上述内容之后 该题目换成一个更准确和更容易理解的代码应该像这样


var a 1,

 b function(x) {

 x b(--x);

alert(a);


这样的话 就很清晰地知道为什么 alert 的总是 1 了。

下面程序输出的结果是


function a(x) {

 return x * 2;

var a;

alert(a);

alert 的值是下面的函数

function a(x) {

 return x * 2;

}


这个题目比较简单 即函数声明和变量声明的关系和影响 遇到同名的函数声明 不会重新定义。


下面程序输出的结果是


function b(x, y, a) {

 arguments[2] 10;

 alert(a);

b(1, 2, 3);


结果为 10。


活动对象是在进入函数上下文时刻被创建的 它通过函数的 arguments 属性初始化。

三道判断输出的题都是经典的题

var a 

function b() {

 console.log(a);

 function a(){};

b();


明显输出是 3 因为里面修改了 a 这个全局变量 那个 function a(){} 是用来干扰的 虽然函数声明会提升 就被 a 给覆盖掉了 这是我的理解。


不记得具体的 就类似如下


var baz 

var bazz {

 baz: 2,

 getbaz: function() {

 return this.baz

console.log(bazz.getbaz())

var g bazz.getbaz;

console.log(g()) ;


第一个输出是 2 第二个输出是 3。


这题考察的就是 this 的指向 函数作为对象本身属性调用的时候 this 指向对象 作为普通函数调用的时候 就指向全局了。


还有下面的题


var arr [1,2,3,4,5];

for(var i i arr.length; i ){

 arr[i] function(){

 alert(i)

arr[3]();


典型的闭包 弹出 5 。

JavaScript 里有哪些数据类型


一、数据类型  

undefiend 没有定义数据类型       number 数值数据类型 例如 10 或者 1 或者 5.5       string 字符串数据类型用来描述文本 例如 你的姓名        boolean 布尔类型 true | false 不是正就是反       object 对象类型 复杂的一组描述信息的集合function 函数类型

解释清楚 null 和 undefined


null 用来表示尚未存在的对象 常用来表示函数企图返回一个不存在的对象。  null 表示 没有对象 即该处不应该有值。


null 典型用法是  


作为函数的参数 表示该函数的参数不是对象。 作为对象原型链的终点。

当声明的变量还未被初始化时 变量的默认值为 undefined。 undefined 表示 缺少值 就是此处应该有一个值 但是还没有定义。 

变量被声明了 但没有赋值时 就等于 undefined。 调用函数时 应该提供的参数没有提供 该参数等于 undefined。 对象没有赋值的属性 该属性的值为 undefined。 函数没有返回值时 默认返回 undefined。

未定义的值和定义未赋值的为 undefined null 是一种特殊的 object NaN 是一种特殊的 number。

讲一下 1 和 Number(1) 的区别*


1 是一个原始定义好的 number 类型 Number(1) 是一个函数类型 是我们自己声明的一个函数 方法 。

讲一下 prototype 是什么东西 原型链的理解 什么时候用 prototype


prototype 是函数对象上面预设的对象属性。

函数里的 this 什么含义 什么情况下 怎么用


this 是 Javascript 语言的一个关键字。它代表函数运行时 自动生成的一个内部对象 只能在函数内部使用。随着函数使用场合的不同 this 的值会发生变化。但是有一个总的原则 那就是 this 指的是 调用函数的那个对象。


情况一 纯粹的函数调用 

这是函数的最通常用法 属于全局性调用 因此 this 就代表全局对象 window。   

function test(){ 

 this.x 

 alert(this.x); 

test(); // 1


为了证明 this 就是全局对象 我对代码做一些改变

   

var x 

function test(){ 

 alert(this.x); 

test(); // 1


运行结果还是 1。


再变一下    

var x 

function test(){ 

 this.x 

test();

alert(x); // 0


情况二 作为对象方法的调用   


函数还可以作为某个对象的方法调用 这时 this 就指这个上级对象。   


function test(){ 

 alert(this.x); 

var x 2 

var o {}; 

o.m test; 

o.m(); // 1


情况三 作为构造函数调用   


所谓构造函数 就是通过这个函数生成一个新对象 object 。这时的 this 就指这个新对象。


function Test(){ 

 this.x 

var o new Test();

alert(o.x); // 1


运行结果为 1。为了表明这时 this 不是全局对象 对代码做一些改变  

 

var x 

function Test(){ 

 this.x 

var o new Test(); 

alert(x); // 2


运行结果为 2 表明全局变量 x 的值没变。


情况四 apply 调用   


apply() 是函数对象的一个方法 它的作用是改变函数的调用对象 它的第一个参数就表示改变后的调用这个函数的对象。因此 this 指的就是这第一个参数。   

var x 

function test(){ 

 alert(this.x); 

var o {}; 

o.m test; 

o.m.apply(); // 0


apply() 的参数为空时 默认调用全局对象。因此 这时的运行结果为 0 证明 this 指的是全局对象。

   

如果把最后一行代码修改为


o.m.apply(o); // 1


运行结果就变成了 1 证明了这时 this 代表的是对象 o。

apply 和 call  什么含义 什么区别 什么时候用


call apply 都属于 Function.prototype 的一个方法 它是 JavaScript 引擎内在实现的 因为属于 Function.prototype 所以每个 Function 对象实例(就是每个方法)都有 call apply 属性。


既然作为方法的属性 那它们的使用就当然是针对方法的了 这两个方法是容易混淆的 因为它们的作用一样 只是使用方式不同。


语法


foo.call(this, arg1, arg2, arg3) foo.apply(this, arguments) this.foo(arg1, arg2, 

arg3);


相同点 两个方法产生的作用是完全一样的。不同点 方法传递的参数不同。


每个函数对象会有一些方法可以去修改函数执行时里面的 this 比较常见得到就是 call 和 apply 通过 call 和 apply 可以重新定义函数的执行环境 即 this 的指向。


function add(c, d) {

 console.log(this.a this.b c d);

var o { a: 1, b: 3 };

add.call(o, 5, 7); //1 3 5 7 16

//传参的时候是扁平的把每个参数传进去

add.apply(o, [10, 20]); //1 3 10 20 34

//传参的时候是把参数作为一个数组传进去

//什么时候使用 call 或者 apply

function bar() {

 console.log(Object.prototype.toString.call(this));

 // 用来调用一些无法直接调用的方法

bar.call(7); // [object Number] 

异步过程的构成要素有哪些 和异步过程是怎样的


总结一下 一个异步过程通常是这样的


主线程发起一个异步请求 相应的工作线程接收请求并告知主线程已收到(异步函数返回) 主线程可以继续执行后面的代码 同时工作线程执行异步任务 工作线程完成工作后 通知主线程 主线程收到通知后 执行一定的动作(调用回调函数)。


异步函数通常具有以下的形式 A(args..., callbackFn)。它可以叫做异步过程的发起函数 或者叫做异步任务注册函数。args 和 callbackFn 是这个函数的参数。


所以 从主线程的角度看 一个异步过程包括下面两个要素


发起函数(或叫注册函数) A。回调函数 callbackFn。


它们都是在主线程上调用的 其中注册函数用来发起异步过程 回调函数用来处理结果。

举个具体的例子


setTimeout(fn, 1000);


其中的 setTimeout 就是异步过程的发起函数 fn 是回调函数。

注意 前面说的形式 A(args..., callbackFn) 只是一种抽象的表示 并不代表回调函数一定要作为发起函数的参数。

例如


var xhr new XMLHttpRequest();

xhr.onreadystatechange xxx; // 添加回调函数

xhr.open( GET , url);

xhr.send(); // 发起函数


发起函数和回调函数就是分离的。

说说消息队列和事件循环


微信图片_20220513225459.png


主线程在执行完当前循环中的所有代码后 就会到消息队列取出这条消息(也就是 message 函数) 并执行它。完成了工作线程对主线程的通知 回调函数也就得到了执行。如果一开始主线程就没有提供回调函数 AJAX 线程在收到 HTTP 响应后 也就没必要通知主线程 从而也没必要往消息队列放消息。异步过程的回调函数 一定不在当前的这一轮事件循环中执行。



JavaScript中的重要组成部分之ES6特性 随着互联网技术的不断发展,前端开发也变得越来越重要。在这样的背景下,ES6(ECMAScript 2015)作为 JavaScript 的一个重要版本,为前端开发带来了许多新特性和功能,本文将介绍一些常用的 ES6 特性。