# 面试难题

back

# JavaScript九大面试问题集锦,助你顺利通关

返回顶部

# 第一部分:突发难题

  • 为什么Math.max()小于Math.min()?
    如果没有给出参数,Math.min()返回infinity(无穷大),Math.max()返回-infinity(无穷小)。这只是max()和min()方法规范的一部分,但选择背后的逻辑值得深议。了解其中原因,请看以下代码:
Math.min(1) // 1
Math.min(1, infinity)// 1
Math.min(1, -infinity)// -infinity
1
2
3

# 第二部分:常见问题

  • 函数表达式与函数声明有哪些不同?
    函数声明使用关键字function,后跟函数的名称。相反,函数表达式以var,let或const开头,后跟函数名称和赋值运算符=。

实际操作中,关键的区别在于函数声明要被提升,而函数表达式则没有。这意味着JavaScript解释器将函数声明移动到其作用域的顶部,因此可以定义函数声明并在代码中的任何位置调用它。相比之下,只能以线性顺序调用函数表达式:必须在调用它之前解释

  • 面向对象编程(OOP)和函数式编程(FP)之间的区别是什么?
    OOP以“对象”这一概念为基础的数据结构,包含数据字段(JavaScript称为类)和程序(JavaScript中的方法)。
    FP(函数编程)以“纯函数”的概念为基础,避免共享状态,可变数据和副作用

  • 是什么基于原型的继承?
    面向对象编程有几种不同的类型,JavaScript使用的是基于原型的继承。该系统通过使用现有对象作为原型,允许重复运行。

例如,用于操作数组的函数(如map,reduce,splice等)都是Array.prototype对象的方法。实际上,数组的每个实例(使用方括号[]定义,或者 -不常见的 new Array())都继承自Array.prototype,这就是为什么map,reduce和spliceare等方法都默认可用的原因。

# 7 个 JavaScript 面试题,看到第 3 个直接崩溃

返回顶部

  • 1、意外创建的全局变量——在以下代码中,typeof a 和 typeof b的值分别是什么:
function foo() {
 let a = b = 0;
 a++;
 return a;
}
foo();
typeof a; // => ???
typeof b; // => ???
1
2
3
4
5
6
7
8

让我们仔细看看第 2 行:let a = b = 0。这个语句确实声明了一个局部变量 a。但是,它确实声明了一个全局变量 b

typeof a 是 'undefined'。变量 a 仅在 foo() 范围内声明,在外部范围内不可用。
typeof b 等于 'number'。b 是一个值为 0 的全局变量。

  • 2、数组 length 属性——clothes[0] 的值是什么
const clothes = ['jacket', 't-shirt'];
clothes.length = 0;
clothes[0]; // => ???
1
2
3

数组对象的 length 属性有一个特殊的行为:
减少 length 属性的值有一个副作用,就是会删除索引位于新旧长度值之间的元素
因为 length 的这种行为,当 JavaScript 执行 clothes.length = 0 时,数组 clothes 中的所有项都被删除了。
clothes[0] 是 undefined,因为 clothes 数组被清空了。

  • 3、鹰眼测试——numbers 数组的内容是什么
const length = 4;
const numbers = [];
for (var i = 0; i < length; i++);{
 numbers.push(i + 1);
}
numbers; // => ???
1
2
3
4
5
6

让我们仔细看看出现在左花括号 { 前面的分号;
很容易忽略这个分号,而它创建了一个空语句。空语句是不做任何事情的语句
这样 numbers 就是 [5]

  • 4、自动插入分号——arrayFromValue() 返回什么值
function arrayFromValue(item) {
 return
 [items];
}
arrayFromValue(10); // => ???
1
2
3
4
5

很容易忽略 return 关键字和 [items] 表达式之间的换行
换行使 JavaScript 自动在 return 和 [items] 表达式之间插入一个分号
因此 arrayFromValue(10) 的值是 undefined

其等价于:

function arrayFromValue(item) {
 return; [items];
}
arrayFromValue(10); // => undefined
1
2
3
4
  • 5、经典问题:坑爹的闭包——以下脚本将会在控制台输出什么:
let i;
for (i = 0; i < 3; i++) {
 const log = () => {
 console.log(i);
 }
 setTimeout(log, 100);
}
1
2
3
4
5
6
7

步骤 1:
for() 迭代 3 次。在每次迭代过程中,都会创建一个新的函数 log(),它捕获变量 i。然后setTimout() 执行 log()。
当 for() 循环完成时,i 变量的值为 3。
log() 是一个捕获变量 i 的闭包,它在 for() 循环的外部作用域定义。重要的是要理解闭包从词法上捕获了变量 i 。 步骤 2:
第 2 步在 100 毫秒后发生:
setTimeout() 调用了队列中的 3 个 log() 回调。log() 读取变量 i 的当前值,即 3,并记录到控制台 3。
这就是为什么控制台输出 3, 3 和 3

  • 6、浮点数问题——等号判断的结果是什么
0.1 + 0.2 === 0.3 // => ???
1

首先,我们看看 0.1 + 0.2 的值:0.1 + 0.2; // => 0.30000000000000004
因此 0.1 + 0.2 === 0.3 是 false

  • 7、变量提升——如果在声明之前访问 myVar 和 myConst 会发生什么?
myVar; // => ???
myConst; // => ???
var myVar = 'value';
const myConst = 3.14;
1
2
3
4

变量提升暂时性死区是影响 JavaScript 变量生命周期的两个重要概念。

# 面试难题之三个让你崩溃

返回顶部

# 事件委托代理

以一个简单的待办事项列表为例,面试官可能会告诉你,当用户点击列表中的一个列表项时执行某些操作

document.addEventListener('DOMContentLoaded', function(){
    let app = document.getElementById('to-do');
    app.addEventListener('click', function(e){
        if(e.target && e.taeget.nodename==='LI'){
            let item = e.target;
            alert('you clicked item:' + item.innerHTML);
        }
    });
});
1
2
3
4
5
6
7
8
9

# 在循环中使用闭包

# 事件的节流(throttle)与防抖(debounce)

# 说说 JavaScript 中的基本类型有哪些?以及各个数据类型是如何存储的

JavaScript 的数据类型包括 原始类型 和 引用类型(对象类型) 。

原始类型包括以下 6 个:

String
Number
Boolean
null
undefined
Symbol
1
2
3
4
5
6

引用类型统称为 Object 类型,如果细分的话,分为以下 5 个:

Object
Array
Date
RegExp
Function
1
2
3
4
5

# 数据类型的存储形式

栈(Stack)和堆(Heap),是两种基本的数据结构。Stack 在内存中自动分配内存空间的;Heap 在内存中动态分配内存空间的,不一定会自动释放。一般我们在项目中将对象类型手动置为 null 原因,减少无用内存消耗。

原始类型是按值形式存放在 栈 中的数据段,内存空间可以自由分配,同时可以 按值直接访问。

引用类型是存放在 堆 内存中,每个对象在堆内存中有一个引用地址,就像是每个房间都有一个房间号一样。引用类型在栈中保存的就是这个对象在堆内存的引用地址,我们所说的“房间号”。通过“房间号”可以快速查找到保存在堆内存的对象。

var obj1 = new Object();
var obj2 = obj1;
obj2.name = "小鹿";
console.log(obj1.name); // 小鹿,此时obj1也变化了
1
2
3
4

# 为什么 typeof null 等于 Object

不同的对象在底层原理的存储是用二进制表示的,在 javaScript 中,如果二进制的前三位都为 0 的话,系统会判定为是 Object 类型。 null 的存储二进制是 000 ,也是前三位,所以系统判定 null 为 Object 类型。

# typeof 与 instanceof 有什么区别

typeof 是一元运算符,同样返回一个字符串类型。一般用来判断一个变量是否为空或者是什么类型。
除了 null 类型以及 Object 类型不能准确判断外,其他数据类型都可能返回正确的类型。

typeof undefined // 'undefined'
typeof '10'      // 'string'
typeof 10        // 'number'
typeof false     // 'boolean'
typeof Symbol()  // 'symbol'
typeof Function  // ‘function'
typeof null      // ‘object’
typeof []        // 'object'
typeof {}        // 'object'
1
2
3
4
5
6
7
8
9

既然 typeof 对对象类型都返回 object 类型情况的局限性,我们可以使用 instanceof 来进行判断 某个对象是不是另一个对象的实例 。返回值的是一个布尔类型

var a = [];
console.log(a instanceof Array) // true
1
2

instanceof 运算符用来测试一个对象在其原型链中是否存在一个构造函数的 prototype 属性

注意

原型链中的 prototype 随时可以被改动的,改变后的值可能不存在于 object 的原型链上, instanceof 返回的值可能就返回 false 。

# 类型转换

javaScript 是一种弱类型语言,变量不受类型限制,所以在特定情况下我们需要对类型进行转换。

「类型转换」分为 显式类型转换 和 隐式类型转换 。每种转换又分为 原始类型转换 和 对象类型转换 。

  • 显式类型转换
    显式类型转换就是我们所说强制类型转换。

对于原始类型来说,转字符串类型会默认调用 toString() 方法。

  • 数据类型String类型数字转化为数字对应的字符串
  • true转化为字符串 "true"
  • null转化为字符串 "null"
  • undefined转化为字符串 “undefined”
  • Object转化为 "[object Object]"

其他数据类型转布尔类型!
除了特殊的几个值 ‘’ 、 undefined 、 NAN 、 null 、 false 、 0 转化为 Boolean 为 false 之外,其他类型值都转化为 true 。

Number(10);        // 10  
Number('10');      // 10  
Number(null);      // 0  
Number('');        // 0  
Number(true);      // 1  
Number(false);     // 0  
Number([]);        // 0
Number([1]);       // 1
Number([1,2]);     // NaN
Number('10a');     // NaN
Number(undefined); // NaN
1
2
3
4
5
6
7
8
9
10
11
  • 隐士类型转化
    是不需要认为的强制类型转化, javaScript 自动将类型转化为需要的类型,所以称之为隐式类型转换。

# 加法运算

加法运算符是在运行时决定,到底是执行相加,还是执行连接。运算数的不同,导致了不同的语法行为,这种现象称为“重载”。

  • Boolean + Boolean 会转化为数字相加。
  • Boolean + Number 布尔类型转化为数字相加。
  • Object + Number 对象类型调用 valueOf ,如果不是 String、Boolean 或者 Number类型,则继续调用 toString() 转化为字符串。
true + true  // 2
1 + true     // 2
[1] + 3      // '13'
(![]+[])[!![]+!![]+!![]]+([]+{})[!![]+!![]]  // "sb"
1
2
3
4

字符串和字符串以及字符串和非字符串相加都会进行 连接 。

1 + 'b'     // ‘1b’
false + 'b' // ‘falseb’
1
2

其他算术运算符(比如减法、除法和乘法)都不会发生重载。它们的规则是:所有运算子一律转为数值,再进行相应的数学运算。

# 什么是 this 指针?以及各种情况下的 this 指向问题

  • 对象调用, this 指向该对象(前边谁调用 this 就指向谁)。
var obj = {
    name:'小鹿',
    age: '21',
    print: function(){
        console.log(this)
        console.log(this.name + ':' + this.age)
    }
}

// 通过对象的方式调用函数
obj.print();        // this 指向 obj
1
2
3
4
5
6
7
8
9
10
11
  • 直接调用的函数, this 指向的是全局 window 对象。
function print(){
    console.log(this);
}
// 全局调用函数
print();   // this 指向 window
1
2
3
4
5
  • 通过 new 的方式, this 永远指向新创建的对象。
function Person(name, age){
    this.name = name;
    this.age = age;
    console.log(this);
}

var xiaolu = new Person('小鹿',22);  // this = > xaiolu
1
2
3
4
5
6
7
  • 箭头函数中的 this 。

由于箭头函数没有单独的 this 值。箭头函数的 this 与声明所在的上下文相同。也就是说调用箭头函数的时候,不会隐士的调用 this 参数,而是从定义时的函数继承上下文。

const obj = {
    a:()=>{
        console.log(this);
    }
}
// 对象调用箭头函数
1
2
3
4
5
6

# 如何改变 this 的指向

过调用函数的 call、apply、bind 来改变 this 的指向

var obj = {
    name:'小鹿',
    age:'22',
    adress:'小鹿动画学编程'
}

function print(){
    console.log(this);       // 打印 this 的指向
    console.log(arguments);  // 打印传递的参数
}

// 通过 call 改变 this 指向
print.call(obj,1,2,3);

// 通过 apply 改变 this 指向
print.apply(obj,[1,2,3]);

// 通过 bind 改变 this 的指向
let fn = print.bind(obj,1,2,3);
fn();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

this

三者都能改变 this 指向,且第一个传递的参数都是 this 指向的对象。
三者都采用的后续传参的形式。

https://www.toutiao.com/a6790607557456036359/?app=news_article&is_hit_share_recommend=0&wxshare_count=1&tt_from=weixin&utm_source=weixin&utm_medium=toutiao_android&utm_campaign=client_share