2024年最全为了面试能通过,我要看完这75道面试题(上),前端进阶面试资料无偿分享

其实前端开发的知识点就那么多,面试问来问去还是那么点东西。所以面试没有其他的诀窍,只看你对这些知识点准备的充分程度。so,出去面试时先看看自己复习到了哪个阶段就好。

这里再分享一个复习的路线:(以下体系的复习资料是我从各路大佬收集整理好的)

开源分享:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】

《前端开发四大模块核心知识笔记》

最后,说个题外话,我在一线互联网企业工作十余年里,指导过不少同行后辈。帮助很多人得到了学习和成长。

我意识到有很多经验和知识值得分享给大家,也可以通过我们的能力和经验解答大家在IT学习中的很多困惑,所以在工作繁忙的情况下还是坚持各种整理和分享。

});

addEvent(document, ‘click’, function (e) {

console.log(‘document’);

});

addEvent(‘html’, ‘click’, function (e) {

console.log(‘html’);

})

addEvent(window, ‘click’, function (e) {

console.log(‘window’);

})

});

addEventListener方法具有第三个可选参数useCapture,其默认值为false,事件将在冒泡阶段中发生,如果为true,则事件将在捕获阶段中发生。 如果单击child元素,它将分别在控制台上记录childparentgrandparenthtmldocumentwindow,这就是事件冒泡。

8. 什么是事件捕获?

当事件发生在 DOM 元素上时,该事件并不完全发生在那个元素上。在捕获阶段,事件从window开始,一直到触发事件的元素。

假设有如下的 HTML 结构:

1

对应的 JS 代码:

function addEvent(el, event, callback, isCapture = false) {

if (!el || !event || !callback || typeof callback !== ‘function’) return;

if (typeof el === ‘string’) {

el = document.querySelector(el);

};

el.addEventListener(event, callback, isCapture);

}

addEvent(document, ‘DOMContentLoaded’, () => {

const child = document.querySelector(‘.child’);

const parent = document.querySelector(‘.parent’);

const grandparent = document.querySelector(‘.grandparent’);

addEvent(child, ‘click’, function (e) {

console.log(‘child’);

});

addEvent(parent, ‘click’, function (e) {

console.log(‘parent’);

});

addEvent(grandparent, ‘click’, function (e) {

console.log(‘grandparent’);

});

addEvent(document, ‘click’, function (e) {

console.log(‘document’);

});

addEvent(‘html’, ‘click’, function (e) {

console.log(‘html’);

})

addEvent(window, ‘click’, function (e) {

console.log(‘window’);

})

});

addEventListener方法具有第三个可选参数useCapture,其默认值为false,事件将在冒泡阶段中发生,如果为true,则事件将在捕获阶段中发生。 如果单击child元素,它将分别在控制台上打印windowdocumenthtmlgrandparentparent,这就是事件捕获

9. event.preventDefault() 和 event.stopPropagation()方法之间有什么区别?

event.preventDefault() 方法可防止元素的默认行为。 如果在表单元素中使用,它将阻止其提交。 如果在锚元素中使用,它将阻止其导航。 如果在上下文菜单中使用,它将阻止其显示或显示。 event.stopPropagation()方法用于阻止捕获和冒泡阶段中当前事件的进一步传播。

10. 如何知道是否在元素中使用了event.preventDefault()方法?

我们可以在事件对象中使用event.defaultPrevented属性。 它返回一个布尔值用来表明是否在特定元素中调用了event.preventDefault()

11. 为什么此代码 obj.someprop.x 会引发错误?

const obj = {};

console.log(obj.someprop.x);

显然,由于我们尝试访问someprop属性中的x属性,而 someprop 并没有在对象中,所以值为 undefined。 记住对象本身不存在的属性,并且其原型的默认值为undefined。因为undefined没有属性x,所以试图访问将会报错。

12. 什么是 event.target ?

简单来说,event.target是发生事件的元素或触发事件的元素。

假设有如下的 HTML 结构:

border:1px solid red;border-radius:3px;">

Button

JS 代码如下:

function clickFunc(event) {

console.log(event.target);

}

如果单击 button,即使我们将事件附加在最外面的div上,它也将打印 button 标签,因此我们可以得出结论event.target是触发事件的元素。

13. 什么是 event.currentTarget??

event.currentTarget是我们在其上显式附加事件处理程序的元素。

假设有如下的 HTML 结构:

border:1px solid red;border-radius:3px;">

Button

JS 代码如下:

function clickFunc(event) {

console.log(event.currentTarget);

}

如果单击 button,即使我们单击该 button,它也会打印最外面的div标签。 在此示例中,我们可以得出结论,event.currentTarget是附加事件处理程序的元素。

14. == 和 === 有什么区别?

==用于一般比较,===用于严格比较,==在比较的时候可以转换数据类型,===严格比较,只要类型不匹配就返回flase

先来看看 == 这兄弟:

强制是将值转换为另一种类型的过程。 在这种情况下,==会执行隐式强制。 在比较两个值之前,==需要执行一些规则。

假设我们要比较x == y的值。

  1. 如果xy的类型相同,则 JS 会换成===操作符进行比较。

  2. 如果xnull, yundefined,则返回true

  3. 如果xundefinedynull,则返回true

  4. 如果x的类型是number, y的类型是string,那么返回x == toNumber(y)

  5. 如果x的类型是string, y的类型是number,那么返回toNumber(x) == y

  6. 如果x为类型是boolean,则返回toNumber(x)== y

  7. 如果y为类型是boolean,则返回x == toNumber(y)

  8. 如果xstringsymbolnumber,而yobject类型,则返回x == toPrimitive(y)

  9. 如果xobjectystringsymbol则返回toPrimitive(x) == y

  10. 剩下的 返回 false

注意:toPrimitive首先在对象中使用valueOf方法,然后使用toString方法来获取该对象的原始值。

举个例子。

| x | y | x == y |

| :-- | :-- | — |

| 5 | 5 | true |

| 1 | ‘1’ | true |

| null | undefined | true |

| 0 | false | true |

| ‘1,2’ | [1,2] | true |

| ‘[object Object]’ | {} | true |

这些例子都返回true

第一个示例符合条件1,因为xy具有相同的类型和值。

第二个示例符合条件4,在比较之前将y转换为数字。

第三个例子符合条件2

第四个例子符合条件7,因为yboolean类型。

第五个示例符合条件8。 使用toString()方法将数组转换为字符串,该方法返回1,2

最后一个示例符合条件8。 使用toString()方法将对象转换为字符串,该方法返回[object Object]

| x | y | x === y |

| :-- | :-- | — |

| 5 | 5 | true |

| 1 | ‘1’ | false |

| null | undefined | false |

| 0 | false | false |

| ‘1,2’ | [1,2] | false |

| ‘[object Object]’ | {} | false |

如果使用===运算符,则第一个示例以外的所有比较将返回false,因为它们的类型不同,而第一个示例将返回true,因为两者的类型和值相同。

具体更多规则可以对参考我之前的文章:

我对 JS 中相等和全等操作符转化过程一直很迷惑,直到有了这份算法

15. 为什么在 JS 中比较两个相似的对象时返回 false?

先看下面的例子:

let a = { a: 1 };

let b = { a: 1 };

let c = a;

console.log(a === b); // 打印 false,即使它们有相同的属性

console.log(a === c); // true

JS 以不同的方式比较对象和基本类型。在基本类型中,JS 通过值对它们进行比较,而在对象中,JS 通过引用或存储变量的内存中的地址对它们进行比较。这就是为什么第一个console.log语句返回false,而第二个console.log语句返回trueac有相同的引用地址,而ab没有。

16. !! 运算符能做什么?

!!运算符可以将右侧的值强制转换为布尔值,这也是将值转换为布尔值的一种简单方法。

console.log(!!null); // false

console.log(!!undefined); // false

console.log(!!‘’); // false

console.log(!!0); // false

console.log(!!NaN); // false

console.log(!!’ '); // true

console.log(!!{}); // true

console.log(!![]); // true

console.log(!!1); // true

console.log(!![].length); // false

17. 如何在一行中计算多个表达式的值?

可以使用逗号运算符在一行中计算多个表达式。 它从左到右求值,并返回右边最后一个项目或最后一个操作数的值。

let x = 5;

x = (x++ , x = addFive(x), x *= 2, x -= 5, x += 10);

function addFive(num) {

return num + 5;

}

上面的结果最后得到x的值为27。首先,我们将x的值增加到6,然后调用函数addFive(6)并将6作为参数传递并将结果重新分配给x,此时x的值为11。之后,将x的当前值乘以2并将其分配给xx的更新值为22。然后,将x的当前值减去5并将结果分配给x x更新后的值为17。最后,我们将x的值增加10,然后将更新的值分配给x,最终x的值为27

18. 什么是提升?

提升是用来描述变量和函数移动到其(全局或函数)作用域顶部的术语。

为了理解提升,需要来了解一下执行上下文执行上下文是当前正在执行的**“代码环境”**。执行上下文有两个阶段:编译执行

编译-在此阶段,JS 引荐获取所有函数声明并将其提升到其作用域的顶部,以便我们稍后可以引用它们并获取所有变量声明(使用var关键字进行声明),还会为它们提供默认值: undefined

执行——在这个阶段中,它将值赋给之前提升的变量,并执行或调用函数(对象中的方法)。

**注意:**只有使用var声明的变量,或者函数声明才会被提升,相反,函数表达式或箭头函数,letconst声明的变量,这些都不会被提升。

假设在全局使用域,有如下的代码:

console.log(y);

y = 1;

console.log(y);

console.log(greet(“Mark”));

function greet(name){

return 'Hello ’ + name + ‘!’;

}

var y;

上面分别打印:undefined,1, Hello Mark!

上面代码在编译阶段其实是这样的:

function greet(name) {

return 'Hello ’ + name + ‘!’;

}

var y; // 默认值 undefined

// 等待“编译”阶段完成,然后开始“执行”阶段

/*

console.log(y);

y = 1;

console.log(y);

console.log(greet(“Mark”));

*/

编译阶段完成后,它将启动执行阶段调用方法,并将值分配给变量。

function greet(name) {

return 'Hello ’ + name + ‘!’;

}

var y;

//start “execution” phase

console.log(y);

y = 1;

console.log(y);

console.log(greet(“Mark”));

19. 什么是作用域?

JavaScript 中的作用域是我们可以有效访问变量或函数的区域。JS 有三种类型的作用域:全局作用域函数作用域块作用域(ES6)

  • 全局作用域——在全局命名空间中声明的变量或函数位于全局作用域中,因此在代码中的任何地方都可以访问它们。

//global namespace

var g = “global”;

function globalFunc(){

function innerFunc(){

console.log(g); // can access “g” because “g” is a global variable

}

innerFunc();

}

  • 函数作用域——在函数中声明的变量、函数和参数可以在函数内部访问,但不能在函数外部访问。

function myFavoriteFunc(a) {

if (true) {

var b = "Hello " + a;

}

return b;

}

myFavoriteFunc(“World”);

console.log(a); // Throws a ReferenceError “a” is not defined

console.log(b); // does not continue here

  • 块作用域-在块{}中声明的变量(let,const)只能在其中访问。

function testBlock(){

if(true){

let z = 5;

}

return z;

}

testBlock(); // Throws a ReferenceError “z” is not defined

作用域也是一组用于查找变量的规则。 如果变量在当前作用域中不存在,它将向外部作用域中查找并搜索,如果该变量不存在,它将再次查找直到到达全局作用域,如果找到,则可以使用它,否则引发错误,这种查找过程也称为作用域链

/* 作用域链

内部作用域->外部作用域-> 全局作用域

*/

// 全局作用域

var variable1 = “Comrades”;

var variable2 = “Sayonara”;

function outer(){

// 外部作用域

var variable1 = “World”;

function inner(){

// 内部作用域

var variable2 = “Hello”;

console.log(variable2 + " " + variable1);

}

inner();

}

outer(); // Hello World

在这里插入图片描述

20. 什么是闭包?

这可能是所有问题中最难的一个问题,因为闭包是一个有争议的话题,这里从个人角度来谈谈,如果不妥,多多海涵。

闭包就是一个函数在声明时能够记住当前作用域、父函数作用域、及父函数作用域上的变量和参数的引用,直至通过作用域链上全局作用域,基本上闭包是在声明函数时创建的作用域。

看看小例子:

// 全局作用域

var globalVar = “abc”;

function a(){

console.log(globalVar);

}

a(); // “abc”

在此示例中,当我们声明a函数时,全局作用域是a闭包的一部分。

在这里插入图片描述

变量globalVar在图中没有值的原因是该变量的值可以根据调用函数a的位置和时间而改变。但是在上面的示例中,globalVar变量的值为abc

来看一个更复杂的例子:

var globalVar = “global”;

var outerVar = “outer”

function outerFunc(outerParam) {

function innerFunc(innerParam) {

console.log(globalVar, outerParam, innerParam);

}

return innerFunc;

}

const x = outerFunc(outerVar);

outerVar = “outer-2”;

globalVar = “guess”

x(“inner”);

在这里插入图片描述

上面打印结果是 guess outer inner

当我们调用outerFunc函数并将返回值innerFunc函数分配给变量x时,即使我们为outerVar变量分配了新值outer-2outerParam也继续保留outer值,因为重新分配是在调用outerFunc之后发生的,并且当我们调用outerFunc函数时,它会在作用域链中查找outerVar的值,此时的outerVar的值将为 "outer"

现在,当我们调用引用了innerFuncx变量时,innerParam将具有一个inner值,因为这是我们在调用中传递的值,而globalVar变量值为guess,因为在调用x变量之前,我们将一个新值分配给globalVar

下面这个示例演示没有理解好闭包所犯的错误:

const arrFuncs = [];

for(var i = 0; i < 5; i++){

arrFuncs.push(function (){

return i;

});

}

console.log(i); // i is 5

for (let i = 0; i < arrFuncs.length; i++) {

console.log(arrFuncsi); // 都打印 5

}

由于闭包,此代码无法正常运行。var关键字创建一个全局变量,当我们 push 一个函数时,这里返回的全局变量i。 因此,当我们在循环后在该数组中调用其中一个函数时,它会打印5,因为我们得到i的当前值为5,我们可以访问它,因为它是全局变量。

因为闭包在创建变量时会保留该变量的引用而不是其值。 我们可以使用IIFES或使用 let 来代替 var 的声明。

21. JavaScript 中的虚值是什么?

const falsyValues = [‘’, 0, null, undefined, NaN, false];

简单的来说虚值就是是在转换为布尔值时变为 false 的值。

22. 如何检查值是否虚值?

使用 Boolean 函数或者 !! 运算符。

23. ‘use strict’ 是干嘛用的?

"use strict"ES5 特性,它使我们的代码在函数或整个脚本中处于严格模式严格模式帮助我们在代码的早期避免 bug,并为其添加限制。

严格模式的一些限制:

  1. 变量必须声明后再使用

  2. 函数的参数不能有同名属性,否则报错

  3. 不能使用with语句

  4. 不能对只读属性赋值,否则报错

  5. 不能使用前缀 0 表示八进制数,否则报错

  6. 不能删除不可删除的属性,否则报错

  7. 不能删除变量delete prop,会报错,只能删除属性delete global[prop]

  8. eval不能在它的外层作用域引入变量

  9. evalarguments不能被重新赋值

  10. arguments不会自动反映函数参数的变化

  11. 不能使用arguments.callee

  12. 不能使用arguments.caller

  13. 禁止this指向全局对象

  14. 不能使用fn.callerfn.arguments获取函数调用的堆栈

  15. 增加了保留字(比如protectedstaticinterface

设立”严格模式”的目的,主要有以下几个:

  1. 消除Javascript语法的一些不合理、不严谨之处,减少一些怪异行为;

  2. 消除代码运行的一些不安全之处,保证代码运行的安全;

  3. 提高编译器效率,增加运行速度;

  4. 为未来新版本的Javascript做好铺垫。

24. JavaScript 中 this 值是什么?

基本上,this指的是当前正在执行或调用该函数的对象的值。this值的变化取决于我们使用它的上下文和我们在哪里使用它。

const carDetails = {

name: “Ford Mustang”,

yearBought: 2005,

getName(){

return this.name;

},

isRegistered: true

};

console.log(carDetails.getName()); // Ford Mustang

这通常是我们期望结果的,因为在getName方法中我们返回this.name,在此上下文中,this指向的是carDetails对象,该对象当前是执行函数的“所有者”对象。

接下我们做些奇怪的事情:

var name = “Ford Ranger”;

var getCarName = carDetails.getName;

console.log(getCarName()); // Ford Ranger

上面打印Ford Ranger,这很奇怪,因为在第一个console.log语句中打印的是Ford Mustang。这样做的原因是getCarName方法有一个不同的“所有者”对象,即window对象。在全局作用域中使用var关键字声明变量会在window对象中附加与变量名称相同的属性。请记住,当没有使用“use strict”时,在全局作用域中this指的是window对象。

console.log(getCarName === window.getCarName); // true

console.log(getCarName === this.getCarName); // true

本例中的thiswindow引用同一个对象。

解决这个问题的一种方法是在函数中使用applycall方法。

console.log(getCarName.apply(carDetails)); // Ford Mustang

console.log(getCarName.call(carDetails)); // Ford Mustang

applycall方法期望第一个参数是一个对象,该对象是函数内部this的值。

IIFE立即执行的函数表达式,在全局作用域内声明的函数,对象内部方法中的匿名函数和内部函数的this具有默认值,该值指向window对象。

(function (){

console.log(this);

})(); // 打印 “window” 对象

function iHateThis(){

console.log(this);

}

iHateThis(); // 打印 “window” 对象

const myFavoriteObj = {

guessThis(){

function getName(){

console.log(this.name);

}

getName();

},

name: ‘Marko Polo’,

thisIsAnnoying(callback){

callback();

}

};

myFavoriteObj.guessThis(); // 打印 “window” 对象

myFavoriteObj.thisIsAnnoying(function (){

console.log(this); // 打印 “window” 对象

});

如果我们要获取myFavoriteObj对象中的name属性(即Marko Polo)的值,则有两种方法可以解决此问题。

一种是将 this 值保存在变量中。

const myFavoriteObj = {

guessThis(){

const self = this; // 把 this 值保存在 self 变量中

function getName(){

console.log(self.name);

}

getName();

},

name: ‘Marko Polo’,

thisIsAnnoying(callback){

callback();

}

};

第二种方式是使用箭头函数

const myFavoriteObj = {

guessThis(){

const getName = () => {

console.log(this.name);

}

getName();

},

name: ‘Marko Polo’,

thisIsAnnoying(callback){

callback();

}

};

箭头函数没有自己的 this。它复制了这个封闭的词法作用域中this值,在这个例子中,this值在getName内部函数之外,也就是myFavoriteObj对象。

25. 对象的 prototype(原型) 是什么?

简单地说,原型就是对象的蓝图。如果它存在当前对象中,则将其用作属性和方法的回退。它是在对象之间共享属性和功能的方法,这也是JavaScript实现继承的核心。

const o = {};

console.log(o.toString()); // logs [object Object]

即使o对象中不存在o.toString方法,它也不会引发错误,而是返回字符串[object Object]。 当对象中不存在属性时,它将查看其原型,如果仍然不存在,则将其查找到原型的原型,依此类推,直到在原型链中找到具有相同属性的属性为止。 原型链的末尾是Object.prototype

console.log(o.toString === Object.prototype.toString); // logs true

26. 什么是 IIFE,它的用途是什么?

IIFE或立即调用的函数表达式是在创建或声明后将被调用或执行的函数。 创建IIFE的语法是,将function (){}包裹在在括号()内,然后再用另一个括号()调用它,如:(function(){})()

(function(){

} ());

(function () {

})();

(function named(params) {

})();

(() => {

});

(function (global) {

})(window);

const utility = (function () {

return {

}

})

这些示例都是有效的IIFE。 倒数第二个救命表明我们可以将参数传递给IIFE函数。 最后一个示例表明,我们可以将IIFE的结果保存到变量中,以便稍后使用。

IIFE的一个主要作用是避免与全局作用域内的其他变量命名冲突或污染全局命名空间,来个例子。

假设我们引入了一个omelibr.js的链接,它提供了一些我们在代码中使用的全局函数,但是这个库有两个方法我们没有使用:createGraphdrawGraph,因为这些方法都有bug。我们想实现自己的createGraphdrawGraph方法。

解决此问题的一种方法是直接覆盖:

当我们使用这个解决方案时,我们覆盖了库提供给我们的那两个方法。

另一种方式是我们自己改名称:

当我们使用这个解决方案时,我们把那些函数调用更改为新的函数名。

还有一种方法就是使用IIFE

在此解决方案中,我们要声明了graphUtility 变量,用来保存IIFE执行的结果,该函数返回一个包含两个方法createGraphdrawGraph的对象。

IIFE 还可以用来解决一个常见的面试题:

var li = document.querySelectorAll(‘.list-group > li’);

for (var i = 0, len = li.length; i < len; i++) {

li[i].addEventListener(‘click’, function (e) {

console.log(i);

})

假设我们有一个带有list-group类的ul元素,它有5li子元素。 当我们单击单个li元素时,打印对应的下标值。但在此外上述代码不起作用,这里每次点击 li 打印 i 的值都是5,这是由于闭包的原因。

闭包只是函数记住其当前作用域,父函数作用域和全局作用域的变量引用的能力。 当我们在全局作用域内使用var关键字声明变量时,就创建全局变量i。 因此,当我们单击li元素时,它将打印5,因为这是稍后在回调函数中引用它时i的值。

使用 IIFE 可以解决此问题:

var li = document.querySelectorAll(‘.list-group > li’);

for (var i = 0, len = li.length; i < len; i++) {

(function (currentIndex) {

li[currentIndex].addEventListener(‘click’, function (e) {

console.log(currentIndex);

})

})(i);

}

该解决方案之所以行的通,是因为IIFE会为每次迭代创建一个新的作用域,我们捕获i的值并将其传递给currentIndex参数,因此调用IIFE时,每次迭代的currentIndex值都是不同的。

27. Function.prototype.apply 方法的用途是什么?

apply() 方法调用一个具有给定this值的函数,以及作为一个数组(或类似数组对象)提供的参数。

const details = {

message: ‘Hello World!’

};

function getMessage(){

return this.message;

}

getMessage.apply(details); // ‘Hello World!’

call()方法的作用和 apply() 方法类似,区别就是call()方法接受的是参数列表,而apply()方法接受的是一个参数数组。

const person = {

name: “Marko Polo”

};

function greeting(greetingMessage) {

return ${greetingMessage} ${this.name};

}

greeting.apply(person, [‘Hello’]); // “Hello Marko Polo!”

28. Function.prototype.call 方法的用途是什么?

call() 方法使用一个指定的 this 值和单独给出的一个或多个参数来调用一个函数。

const details = {

message: ‘Hello World!’

};

function getMessage(){

return this.message;

}

getMessage.call(details); // ‘Hello World!’

最后

全网独播-价值千万金融项目前端架构实战

从两道网易面试题-分析JavaScript底层机制

RESTful架构在Nodejs下的最佳实践

开源分享:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】

一线互联网企业如何初始化项目-做一个自己的vue-cli

思维无价,看我用Nodejs实现MVC

代码优雅的秘诀-用观察者模式深度解耦模块

前端高级实战,如何封装属于自己的JS库

VUE组件库级组件封装-高复用弹窗组件

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值