前端高频面试题—JavaScript篇(一)

news/2024/11/20 13:37:09/

💻 前端高频面试题—JavaScript篇(一) 🏠专栏:前端面试题
👀个人主页:繁星学编程🍁
🧑个人简介:一个不断提高自我的平凡人🚀
🔊分享方向:目前主攻前端,其他知识也会阶段性分享🍀
👊格言:☀️没有走不通的路,只有不敢走的人!☀️
👉让我们一起进步,一起成为更好的自己!!!🎁

文章目录

  • 前端高频面试题—JavaScript篇(一)
    • 一. 讲讲JS的数据类型
      • 1. 基本数据类型(值类型)
      • 2. 引用数据类型
      • 3. NaN是什么类型?
      • 4. null和undefined区别
      • 5. Symbol用法
      • 6. BigInt
    • 二. 检测数据类型
      • 判断数据类型的方式
    • 三. 作用域
      • 什么是作用域?
      • (1) 全局作用域
      • (2) 局部作用域
      • (3) 块级作用域
      • 特殊情况
    • 四. 作用域链
      • (1) 概念
      • (2) 访问规则
      • (3) 赋值规则
      • (4) 案例
      • 相关面试题
        • 面试题一
        • 面试题二
    • 五. 预解析
      • 一.概述
      • 二. 解读预解析两步
        • 预解析的两大步骤
        • 案例1
        • 案例2
        • 案例3
        • 案例4

前端高频面试题—JavaScript篇(一)

本文主要讲解的前端高频面试题知识有:

  1. 讲讲JS的数据类型
  2. 检测数据类型
  3. 作用域
  4. 作用域链
  5. 预解析

一. 讲讲JS的数据类型

1. 基本数据类型(值类型)

基本类型:字符串(String)、数字(Number)、布尔(Boolean)、对空(Null)、未定义(Undefined)、Symbol。

2. 引用数据类型

引用数据类型:对象(Object)、数组(Array)、函数(Function)。(数组和函数都是属于对象的数据类型)

3. NaN是什么类型?

NaN是一个数值类型(number),但不是一个具体数字

console.log(typeof NaN) // number

4. null和undefined区别

null会被隐式转化为0,不容易发现错误,先有null后有undefined,undefined就是为了填补之前的坑(null表示一个空对象指针,转化为数值为0;undefined转化为数值是NaN)

5. Symbol用法

注:Symbol 是 ES6 引入了一种新的原始数据类型,表示独一无二的值。

Symbol 本质上是一种唯一标识符,可用作对象的唯一属性名,这样其他人就不会改写或覆盖你设置的属性值。

const a = Symbol();
console.log(a); // Symbol()
const b = Symbol("test");
console.log(b); // Symbol('test');
const c = Symbol();
console.log(c == a); // false
console.log(c.toString()); // 'Symbol()'
var mySymbol = Symbol();
//第一种写法
var a1 = {};
a1[mySymbol] = "Hello!";
console.log(a1[mySymbol]); // Hello!
//第二种写法
var a2 = {[mySymbol]: "Hellow!",
};
console.log(a2[mySymbol]); // Hellow!
//第三种写法
var a3 = {};
Object.defineProperty(a3, mySymbol, { value: "Hellow!" });
console.log(a3[mySymbol]); // Hellow!

Symbol需要注意:

  1. symbol函数前不能使用new关键字,否则会报错,这是因为symbol是原始数据类型,而不是对象,所以不能添加属性
  2. symbol可以接受一个字符串作为参数,表示对Symbol的描述,主要是在控制台显示时容易区分。这个参数可以不加,如果不加在控制台输出就是两个Symbol()不利于区分,加上参数就是为了加以区分。
  3. Symbol是唯一的与谁都不相等
  4. Symbol不能与其他值进行运算,否则会报错
  5. Symbol 可以显示的转为字符串,布尔值,但是不能转为数字,转为数字会报错
  6. 由于每一个Symbol都不相同,那么可以作为标识符作为对象的属性名,保证不会出现同名的的属性
  7. Symbol值作为对象的属性名时不能使用点运算符,同理,在对象的内部使用Symbol值时也必须放在方括号中

6. BigInt

BigInt 是一种内置对象,它提供了一种方法来表示大于 2^53 - 1 的整数。这原本是 Javascript 中可以用Number 表示的最大数字。BigInt 可以表示任意大的整数。

新增加这个数据类型的目的:js存储16位以上的数据会出现精度丢失的问题

var num = 100;
num = 100n; // 转为BigInt
console.log(num); // 100n
console.log(typeof num); // bigintconsole.log(100n + 100n); // 200n
console.log(100n + 100); // Uncaught TypeError: Cannot mix BigInt and other types, use explicit conversions

  • 将任意数字转为BigInt , 在数字后面添加n

  • bigInt只能和bigInt一起操作

  • BigIntNumber不是严格相等的,但是宽松相等的。

    10n == 10; // true
    10n === 10; // false
    

二. 检测数据类型

判断数据类型的方式

  1. typeof

    语法:typeof(要测试的数据)
    返回值:测试数据对应的数据类型
    缺点:只能检测基本数据类型

    console.log(typeof (12)); // number
    console.log(typeof ('null')); // string
    console.log(typeof (null)); // object
    console.log(typeof (undefined)); // undefined
    console.log(typeof (true)); // boolean
    console.log(typeof ([])); // object
    console.log(typeof ({})); // object
    

    :要注意null的数据类型是Object

  2. constructor

    语法:数据结构.constructor
    返回值:该数据结构所属的构造函数
    缺点:无法检测 undefined 和 null

    console.log([].constructor); // ƒ Array() { [native code] }
    console.log({}.constructor); // ƒ Object() { [native code] }
    console.log(function () { }.constructor); // ƒ Function() { [native code] }
    console.log((1).constructor); // ƒ Number() { [native code] }
    console.log(('zs').constructor); // ƒ String() { [native code] }
    console.log((null).constructor); // Uncaught TypeError: Cannot read properties of null (reading 'constructor')
    console.log((undefined).constructor); // Uncaught TypeError: Cannot read properties of undefined (reading 'constructor')
    
  3. instanceof

    语法:数据结构 instanceof 构造函数
    返回值:true/false
    缺点:无法检测 undefined 和 null

    console.log([] instanceof Array); // true
    console.log([] instanceof Object); // true
    console.log([] instanceof String); // false
    console.log(null instanceof Object); // false
    console.log(undefined instanceof Object); // false
    let a = 1;
    console.log(a instanceof Number); // false
    
  4. Object.prototype.toString.call()

    语法:Object.prototype.toString.call(要测试的数据)
    返回值:‘[Object 数据类型]’
    所有数据类型都可以检测

    console.log(Object.prototype.toString.call(1)); // [object Number]
    console.log(Object.prototype.toString.call([])); // [object Array]
    console.log(Object.prototype.toString.call({})); // [object Object]
    console.log(Object.prototype.toString.call(true)); // [object Boolean]console.log(Object.prototype.toString.call(123)); // [object Number]
    console.log(Object.prototype.toString.apply(123)); // [object Number]
    console.log(Object.prototype.toString.bind(123)()); // [object Number]
    

    :不必一定用call()也可以使用bind()()apply(),只要是立即执行即可。

总结区别

  • typeof只能检测基本数据类型
  • constructor 无法检测 undefinednull
  • instanceof 无法检测 undefinednull
  • Object.prototype.toString.call() 所有数据类型都可以检测

三. 作用域

JavaScript中的作用域主要有:全局作用域、函数(局部)作用域、块级作用域

什么是作用域?

就是一个变量可以生效(访问)的范围,变量不是在所有地方都可以使用的,而这个变量的使用范围就是作用域。

目的:为了提高程序的可靠性和减少命名冲突

(1) 全局作用域

直接在script标签中编写的代码都运行在全局作用域中。

任何不在函数中或是大括号中声明的变量,都是在全局作用域下,全局作用域下声明的变量可以在程序的任意位置访问。

<script>let a = 10;console.log(a);// 10function fn() {console.log(a); // 10a = a + 10;console.log(a); // 20return a;}fn()console.log(a); // 20
</script>

注意事项

  1. 全局作用域在打开页面时创建,在页面关闭时销毁。
  2. 全局作用域中有一个全局对象window,window对象由浏览器提供,可以在页面中直接使用,它代表的是整个的浏览器的窗口。
  3. 在全局作用域中创建的变量都会作为window对象的属性保存,在全局作用域中创建的函数都会作为window对象的方法保存。
  4. 在全局作用域中创建的变量和函数可以在页面的任意位置访问。在函数作用域中也可以访问到全局作用域的变量。

(2) 局部作用域

**局部作用域(也叫函数作用域)**就是在全局作用域下面有开辟出来的一个相对小一些的作用域,在局部作用域中定义的变量只能在这个局部作用域内部使用。

在JavaScript中只有函数能生成一个局部作用域, 别的都不行。

var a = 10;
function fn(){var a = 20;console.log(a);
}
fn();// 20

注意事项

  1. 函数作用域是函数执行时创建的作用域,每次调用函数都会创建一个新的函数作用域。

  2. 函数作用域在函数执行时创建,在函数执行结束时销毁。

  3. 在函数作用域中创建的变量,不能在全局中访问。

  4. 当在函数作用域中使用一个变量时,它会先在自身作用域中寻找:

    如果找到了则直接使用,如果没有找到则到上一级作用域中寻找;

    如果找到了则使用,找不到则一直会继续向上找。

var a = 10; // 全局作用域
function fn() {var b = 100; // 局部作用域  console.log(a); // 10  a是全局console.log(b); // 100 b是局部,函数内部使用
}
fn();
console.log(a); // 10  a是全局
console.log(b); // 报错,b是局部,函数外面无法访问

(3) 块级作用域

ES6引入了letconst关键字,和var关键字不同,在大括号中使用letconst声明的变量存在于块级作用域中。在大括号之外不能访问这些变量。

if (10 > 1) {let num = 100;console.log(num); // 100const a = 200; console.log(a); // 200
}
console.log(a); // 报错:Uncaught ReferenceError: a is not defined
console.log(num);

特殊情况

如果函数内部声明的变量,声明的时候没有添加var关键字,函数内部声明的变量视为全局变量。

(1) 外部定义了全局变量,函数内部直接改变这个全局变量

var a = 10; //全局
function fn() {a = 100; //可以使用全局,改变全局console.log(a); //100  全局
}
fn();
console.log(a); //100  全局

(2) 外部没有定义了全局变量,而是忽略的var关键字,b变成全局变量

function fn1() {b = 200; console.log(b); //200
}
fn1();
console.log(b); //200

四. 作用域链

(1) 概念

内部函数访问外部函数的变量,采用的是链式查找的方式来决定取哪个值。

var a = 10;
function fn() {var a = 20;function fn2() {console.log(a);}fn2();
}
fn();// 20

有了作用域以后,变量就有了使用范围,也就有了使用规则

变量使用规则分为两种:访问规则赋值规则

(2) 访问规则

// 当我想获取一个变量的值的时候,我们管这个行为叫做访问
var a = 10;
console.log(a);//访问

首先,在自己的作用域内部查找

  • 如果有,就直接拿来使用;
  • 如果没有,就去上一级作用域查找,如果有,就拿来使用;
  • 如果没有,就继续去上一级作用域查找,依次类推;
  • 如果一直到全局作用域都没有这个变量,那么就会直接报错(该变量 is not defined)
var a = 10; // 全局作用域 全局变量
function fn1() {var a = 100; // 局部变量 f1作用域中的局部变量function fn2() {// var a = 1000; // 局部变量  f2作用域中的局部变量console.log(a); }fn1();
}
fn(); // 100

(3) 赋值规则

当你想给一个变量赋值的时候,那么就先要找到这个变量,再给他赋值。

  • 先在自己作用域内部查找,有就直接赋值;
  • 没有就去上一级作用域内部查找,有就直接赋值;
  • 还没有再去上一级作用域查找,有就直接赋值;
  • 如果一直找到全局作用域都没有,那么就把这个变量定义为全局变量,再给他赋值。
function fn() {var a = b = 3; // b = 3   var a = b;console.log(a, b); // 3,3
}
fn();
console.log(b); // 3 // 注:b的声明前面没有var关键字,变成全局的
console.log(a); // 报错,a是局部变量

(4) 案例

案例1

先在自己作用域内部查找,有就直接赋值;

var n = 100;
function f1() {var n = 200;console.log("f1 前", n); // 找自己作用域中的变量n, 如果有,直接使用n = 500; // 赋值,先找自己作用域中的变量n,如果有,直接赋值,这里把自己作用域中的n修改掉console.log("f1 后", n); // 访问,找自己作用域中的变量n,如果有,直接使用
}
console.log("全局前", n); // 访问规则,找全局变量n n = 100
f1();
console.log("全局后", n); // 访问机制,找全局变量n n = 100

案例2

没有就去上一级作用域内部查找,有就直接赋值;
还没有再去上一级作用域查找,有就直接赋值;

var n = 100;
function f1() {console.log("f1 前", n); // 第二个打印,在自己作用域中找,向上一级找,找到window 中有一个n n === 100n = 500; // 赋值 ,在自己作用域中没找到,向上一级找,找到window 中有一个n n === 500console.log("f1 后", n); // 第三个打印,在自己作用域中找没找到,向上一级找,找到window中有一个n  n === 500
}
console.log("全局前", n); // 第一个打印,自己作用域(window) 找到全局变量n n = 100
f1(); // 第二步 函数调用,找到函数体里面的代码执行
console.log("全局后", n); // 第四个打印,找自己作用域(window) 找到了 n === 500

案例3

如果一直找到全局作用域都没有,那么就把这个变量定义为全局变量,再给他赋值。

function f1() {n = 500; // 赋值  从自己作用域中找,没找到,向上找,找到window ,window没有,直接把这个n当做全局变量n进行赋值console.log("f1后", n); // 从自己找,没找到向上找,找到window window有一个全局变量n n === 500
}
f1();
console.log("全局", n); // 从自己找,没找到向上找,找到window,window有一个全局变量n n === 500

相关面试题

面试题一

var a = b;
a = 20;
b = 20;
console.log(a); // 报错,b is not defined
console.log(b); 

解题思路

  1. 打开浏览器

  2. 预解析

    // 只有第一行代码需要预解析
    // 提前声明一个变量a 不赋值
    
  3. 代码执行

    // a = b 赋值
    // 把b变量对应的值赋给a
    // 有没有b这个变量, 在作用域中找到b这个变量,报错 b is not defined 
    

面试题二

var a = b = 10
a = 20
b = 20
console.log(a) // 20
console.log(b) // 20

解题思路

  1. 打开浏览器

  2. 进行预解析

    // 只有第一行代码需要预解析
    // 提前声明一个变量a 不赋值
    
  3. 代码执行

    // 1.执行第一行代码// var a = b = 10 实际上等同于 var a = (b = 10) // b = 10要先处理,按照赋值机制处理,这里把b定义为全局变量再进行赋值// 然后再将b中得到的10赋值给a
    // 2.// a = 20 给a重新赋值为20// b = 20 给b重新赋值为20
    // 3. // console.log(a) 打印a的值 // 20// console.log(b) 打印b的值 // 20
    

五. 预解析

一.概述

在代码执行之前,对代码进行通读并解析

首先,思考两个问题

为什么变量可以变量提升,并且同时输出undefined?

console.log(a); // undefined
var a = 1;
console.log(a); // 1

为什么声明式函数可以在任意地方进行调用?

fn();
function(){console.log(1);
}
fn();

针对以上的两个问题,就要说到预解析的作用了。

预解析特点

  1. 预解析只会解析 var 声明的变量声明式函数
  2. 赋值式函数,按照var 的方式进行预解析
  3. 函数里面的变量,不会进行预解析,
  4. 函数里面的代码会在该函数调用的时候,进行预解析

预解析:解析 var 声明的变量

提前声明这一个变量,但是不赋值

预解析:解析 声明式函数

提前声明这个变量,并且赋值为一个函数

二. 解读预解析两步

预解析的两大步骤

(1) 预编译,代码进入浏览器逐行执行之前干的事情(不可见)(案例1-4会分别解释以下四句话)

  1. 先找var和function关键字,找到var,提前赋值undefined找到function,提前将整个函数体赋值给函数名称
  2. 如果预编译函数名和变量名出现重复,函数名优先(去除变量名)
  3. 函数内部依然做预编译,同时函数的参数类似函数内部的变量,也要做预编译。
  4. 函数如果带有形参,要先形参赋值,在进行预解析

(2) 逐行执行,代码进入浏览器,可以根据浏览器返回的信息,查看结果,同时遇到代码错误,立刻停止执行。

函数声明直接跳过,函数必须调用才有意义。

案例1

分析预编译的结果:提前进入浏览器的引擎
先找var和function关键字,找到var,提前赋值undefined找到function,提前将整个函数体赋值给函数名称

// 要执行的代码
console.log(a);
fn();
var a = 10; 
console.log(a); 
function fn() {console.log('函数');
}
fn();

分析思路

// 1.首先对上述代码进行预编译:
a = undefined;
fn = function fn() {console.log('函数');}// 2.然后,进入逐行执行:
console.log(a); // 输出:undefined
fn(); // 输出:函数
var a = 10; // 这里将预编译的结果进行覆盖:a = 10
console.log(a); // 输出:10
function fn() {console.log('函数');
}
fn(); // 输出:函数

案例2

分析预编译的结果:提前进入浏览器的引擎
如果预编译函数名和变量名出现重复,函数名优先(去除变量名)

// 要执行的代码
console.log(a); 
var a = 10; 
console.log(a); 
function a() {console.log('函数');
}
console.log(a); 

分析思路

// 1.首先,对上述代码进行预编译:
// 由于函数名和变量名出现重复,函数名优先(去除变量名)
a =  function a() {console.log('函数');}// 2.然后,进入逐行执行:
console.log(a); // 输出:function a() {console.log('函数');}
var a = 10; // 修改预编译的结果: a = 10
console.log(a); // 输出:10
function a() {console.log('函数');
}
console.log(a); // 输出:10

案例3

分析函数内部预编译的结果:提前进入浏览器的引擎
函数内部依然做预编译,同时函数的参数类似函数内部的变量,也要做预编译。

注意:只要函数存在形参,一定要做预编译。

// 要执行的代码
var a = 1;
function fn() {console.log(a); var a = 20;console.log(a); 
}
console.log(fn());

分析思路

// 1.首先进行预编译:
a = undefined;
fn = function fn(a) { console.log(a); a = 20; console.log(a); }
// 注意:只要函数存在形参,一定要做预编译。
// 2.代码依次执行
var a = 1;
function fn() { // 2.1首先函数内部预解析a = undefined;// 2.2然后执行代码console.log(a); // 输出:undefinedvar a = 20;console.log(a); // 修改预编译的结果: a = 20
}
console.log(fn()); // 输出:undefined(因为函数没有写return,没有返回值)

案例4

分析函数内部预编译的结果:提前进入浏览器的引擎
函数内部要做预编译,同时函数如果带有形参,要先形参赋值,在进行预解析。

// 要执行的代码
var a = 1;
function fn(a) {console.log(a); var a = 20;console.log(a); 
}
console.log(fn(a));

分析思路

// 1.首先进行预编译:
a = undefined;
fn = function fn(a) { console.log(a); a = 20; console.log(a); }
// 注意:只要函数存在形参,一定要做预编译。
// 2.代码依次执行
var a = 1;
function fn(a) { // 2.1首先函数内部预解析a = undefined;// 2.2然后执行代码console.log(a); // 输出:1(因为在预解析前先进行了形参赋值)var a = 20;console.log(a); // 修改预编译的结果: a = 20return a;
}
console.log(fn(a)); // 输出:20

结束语

希望对您有一点点帮助,如有错误欢迎小伙伴指正。
👍点赞:您的赞赏是我前进的动力!
⭐收藏:您的支持我是创作的源泉!
✍评论:您的建议是我改进的良药!
一起加油!!!💪💪💪


http://www.ppmy.cn/news/24153.html

相关文章

Pandas——Series操作【建议收藏】

pandas——Series操作 作者&#xff1a;AOAIYI 创作不易&#xff0c;觉得文章不错或能帮助到你学习&#xff0c;可以点赞收藏评论哦 文章目录pandas——Series操作一、实验目的二、实验原理三、实验环境四、实验内容五、实验步骤1.创建Series2.从具体位置的Series中访问数据3.使…

有趣的KaTeX(附源码)

两年半未见&#xff0c;甚是想念 给大家带来有趣的KaTeX\KaTeXKATE​X&#xff0c;可以放在洛谷主页 文章目录1234561 1#include<bits/stdc.h>\texttt{1 \color{orange}\#include <bits/stdc.h>}1 #include <bits/stdc.h> 2usingnamespacestd;\texttt{2 \col…

Python_pytorch

python_pytorch 小土堆pytotch学习视频链接 from的是一个个的包&#xff08;package) import 的是一个个的py文件(file.py) 所使用的一般是文件中的类(.class) 第一步实例化所使用的类,然后调用类中的方法&#xff08;def) Dataset 数据集处理 import os from PIL impo…

亿级高并发电商项目-- 实战篇 --万达商城项目 二(Zookeeper、Docker、Dubbo-Admin等搭建工作

&#x1f44f;作者简介&#xff1a;大家好&#xff0c;我是小童&#xff0c;Java开发工程师&#xff0c;CSDN博客博主&#xff0c;Java领域新星创作者 &#x1f4d5;系列专栏&#xff1a;前端、Java、Java中间件大全、微信小程序、微信支付、若依框架、Spring全家桶 &#x1f4…

【数据结构】双向链表的模拟实现(无头)

目录 前言&#xff1a; 1、认识双向链表中的结点 2、认识并创建无头双向链表 3、实现双向链表当中的一些方法 3.1、遍历输出方法&#xff08;display&#xff09; 3.2、得到链表的长度&#xff08;size&#xff09; 3.3、查找关键字key是否包含在双链表中(contains) 3.…

【博学谷学习记录】大数据课程-学习第六周总结

Hadoop 3.x的版本架构和模型介绍 由于Hadoop 2.0是基于JDK 1.7开发的&#xff0c;而JDK 1.7在2015年4月已停止更新&#xff0c;这直接迫使Hadoop社区基于JDK 1.8重新发布一个新的Hadoop版本&#xff0c;即hadoop 3.0。Hadoop 3.0中引入了一些重要的功能和优化&#xff0c;包括…

【Python】正则表达式简单教程

0x01 正则表达式概念及符号含义 掌握正则表达式&#xff0c;只需要记住不同符号所表示的含义&#xff0c;以及对目标对象模式&#xff08;或规律&#xff09;的正确概括。 1、基础内容 字符匹配 在正则表达式中&#xff0c;如果直接给出字符&#xff0c;就是精确匹配。\d 匹…

二叉搜索树之AVL树

AVL树的概念二叉搜索树虽可以缩短查找的效率&#xff0c;但如果数据有序或接近有序二叉搜索树将退化为单支树&#xff0c;查找元素相当于在顺序表中搜索元素&#xff0c;效率低下。因此&#xff0c;两位俄罗斯的数学家G.M.Adelson-Velskii和E.M.Landis在1962年 发明了一种解决上…