2023前端面试整理-JS篇

JavaScript 部分

JavaScript基础

1. 介绍下JavaScript 数据类型

JavaScript 共有八种数据类型,分别是 Undefined、Null、Boolean、Number、String、Object、Symbol、BigInt。
其中 Symbol 和 BigInt 是 ES6 中新增的数据类型:

  • Symbol 代表创建后独一无二且不可变的数据类型,它主要是为了解决可能出现的全局变量冲突的问题。
  • BigInt 是一种数字类型的数据,它可以表示任意精度格式的整数,使用 BigInt 可以安全地存储和操作大整数,即使这个数已经超出了Number 能够表示的安全整数范围。
    这些数据可以分为原始数据类型和引用数据类型:
  • 栈:原始数据类型(Undefined、Null、Boolean、Number、String)
  • 堆:引用数据类型(对象、数组和函数)
    两种类型的区别在于存储位置的不同:
  • 原始数据类型直接存储在栈(stack)中的简单数据段,占据空间小、大小固定,属于被频繁使用数据,所以放入栈中存储;
  • 引用数据类型存储在堆(heap)中的对象,占据空间大、大小不固定。如果存储在栈中,将会影响程序运行的性能;引用数据类型在栈中存储了指针,该指针指向堆中该实体的起始地址。当解释器寻找引用值时,会首先检索其在栈中的地址,取得地址后从堆中获得实体。堆和栈的概念存在于数据结构和操作系统内存中,在数据结构中:
  • 在数据结构中,栈中数据的存取方式为先进后出。
  • 堆是一个优先队列,是按优先级来进行排序的,优先级可以按照大小来规定。
    在操作系统中,内存被分为栈区和堆区:
  • 栈区内存由编译器自动分配释放,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。
  • 堆区内存一般由开发着分配释放,若开发者不释放,程序结束时可能由垃圾回收机制回收。

2. 数据类型检测的方式有哪些

  • typeof
    其中数组、对象、null 都会被判断为 object,其他判断都正确。
  • instanceof
    instanceof 可以正确判断对象的类型,其内部运行机制是判断在其原型链中能否找到该类型的原型。
    可以看到,instanceof 只能正确判断引用数据类型,而不能判断基本数据类型。instanceof 运算符可以用来测试一个对象在其原型链中是否存在一个构造函数的 prototype 属性。
  • constructor
    constructor 有两个作用,一是判断数据的类型,二是对象实例通过constrcutor 对象访问它的构造函数。需要注意,如果创建一个对象来改变它的原型,constructor 就不能用来判断数据类型了。
  • Object.prototype.toString.call()
    Object.prototype.toString.call() 使用 Object 对象的原型方法 toString 来判断数据类型。
    同样是检测对象 obj 调用 toString 方法,obj.toString()的结果和Object.prototype.toString.call(obj)的结果不一样,这是为什么?
    这是因为 toString 是 Object 的原型方法,而 Array、function 等类型作为 Object 的实例,都重写了 toString 方法。不同的对象类型用 toString 方法时,根据原型链的知识,调用的是对应的重写之后的 toString 方法(function 类型返回内容为函数体的字符串,Array类型返回元素组成的字符串),而不会去调用 Object 原型上的 toString 方法(返回对象的具体类型),所以采用 obj.toString()不能得到其对象类型,只能将 obj 转换为字符串类型。因此,在想要得到对象的具体类型时,应该调用 Object 原型上的 toString 方法。

3. null 和 undefined 区别

首先 Undefined 和 Null 都是基本数据类型,这两个基本数据类型分别都只有一个值,就是 undefined 和 null。
undefined 代表的含义是未定义,null 代表的含义是空对象。一般变量声明了但还没有定义的时候会返回 undefined,null 主要用于赋值给一些可能会返回对象的变量,作为初始化。
undefined 在 JavaScript 中不是一个保留字,这意味着可以使用undefined 来作为一个变量名,但是这样的做法是非常危险的,它会影响对 undefined 值的判断。我们可以通过一些方法获得安全的undefined 值,比如说 void 0。
当对这两种类型使用 typeof 进行判断时,Null 类型化会返回“object”,这是一个历史遗留的问题。当使用双等号对两种类型的值进行比较时会返回 true,使用三个等号时会返回 false。

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

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

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

  while (true) {
   
    if (!proto) return false;
    if (proto === prototype) return true;
    // 如果没有找到,就继续从其原型上找,Object.getPrototypeOf方法用来获取指定对象的原型
    proto = Object.getPrototypeOf(proto);
  }
}

原理:
instanceof 是一个运算符,它可以用来判断某一个对象的类型,具体原理就是利用了原型和原型链(判断构造函数的prototype属性是否出现在对象的原型链中)。
instanceof =>通过 原型和原型链 判断一个实例对象与一个构造函数之间的关系的.
instanceof 左侧是一个实例对象,右侧是一个构造函数。

更简单的说法:
我们拿到 instanceof 左侧对象的原型链
再拿到 instanceof 右侧构造函数的显式原型 prototype
如果原型链中存在显式原型 prototype,instanceof 返回 true,否则返回 false

5. 如何获取安全的 undefined 值?

因为 undefined 是一个标识符,所以可以被当作变量来使用和赋值,但是这样会影响 undefined 的正常判断。表达式 void ___ 没有返回值,因此返回结果是 undefined。void 并不改变表达式的结果,只是让表达式不返回值。因此可以用 void 0 来获得 undefined。

6. Object.is() 与比较操作符 “=”、“” 的区别?

使用双等号()进行相等判断时,如果两边的类型不一致,则会进行强制类型转化后再进行比较。使用三等号(=)进行相等判断时,如果两边的类型不一致时,不会做强制类型准换,直接返回 false。
使用 Object.is 来进行相等判断时,一般情况下和三等号的判断相同,它处理了一些特殊的情况,比如 -0 和 +0 不再相等,两个 NaN是相等的。

7. 什么是 JavaScript 中的包装类型?

在 JavaScript 中,基本类型是没有属性和方法的,但是为了便于操作基本类型的值,在调用基本类型的属性或方法时 JavaScript 会在后台隐式地将基本类型的值转换为对象,如:
在访问 ‘abc’.length 时 ,JavaScript 将 ‘abc’ 在 后 台 转 换 成String(‘abc’),然后再访问其 length 属性。
JavaScript 也可以使用 Object 函数显式地将基本类型转换为包装类型;也可以使用 valueOf 方法将包装类型倒转成基本类型。

8. 为什么会有 BigInt 的提案?

JavaScript 中 Number.MAX_SAFE_INTEGER 表示最⼤安全数字,计算结果是 9007199254740991,即在这个数范围内不会出现精度丢失(⼩数除外)。但是⼀旦超过这个范围,js 就会出现计算不准确的情况,这在⼤数计算的时候不得不依靠⼀些第三⽅库进⾏解决,因此官⽅提出了 BigInt 来解决此问题。

9. 如何判断一个对象是空对象

使用 JSON 自带的.stringify 方法来判断:

var a={
   };
var b=new Object();
console.log(JSON.stringify(a)=="{}") //true
console.log(JSON.stringify(b)=="{}") //true

使用 ES6 新增的方法 Object.keys()来判断

var data = {
   };
var arr = Object.keys(data);
console.log(arr.length == 0);//true

10. const 对象的属性可以修改吗

const 保证的并不是变量的值不能改动,而是变量指向的那个内存地址不能改动。对于基本类型的数据(数值、字符串、布尔值),其值就保存在变量指向的那个内存地址,因此等同于常量。
但对于引用类型的数据(主要是对象和数组)来说,变量指向数据的内存地址,保存的只是一个指针,const 只能保证这个指针是固定不变的,至于它指向的数据结构是不是可变的,就完全不能控制了。

11. 如果 new 一个箭头函数的会怎么样

箭头函数是 ES6 中的提出来的,它没有 prototype,也没有自己的 this指向,更不可以使用 arguments 参数,所以不能 New 一个箭头函数。
new 操作符的实现步骤如下:
(1) 创建一个对象
(2) 将构造函数的作用域赋给新对象(也就是将对象的proto属性指向构造函数的 prototype 属性)
(3) 指向构造函数中的代码,构造函数中的 this 指向该对象(也就是为这个对象添加属性和方法)
(4) 返回新的对象
所以,上面的第二、三步,箭头函数都是没有办法执行的。

12. 箭头函数的 this 指向哪⾥?

箭头函数不同于传统 JavaScript 中的函数,箭头函数并没有属于⾃⼰的 this,它所谓的 this 是捕获其所在上下⽂的 this 值,作为⾃
⼰的 this 值,并且由于没有属于⾃⼰的 this,所以是不会被 new
调⽤的,这个所谓的 this 也不会被改变。
可以⽤Babel 理解⼀下箭头函数:

// ES6 
const obj = {
    
  getArrow() {
    
    return () => {
    
      console.log(this === obj); 
    }; 
  } 
}

转化后:

// ES5,由 Babel 转译
var obj = {
    
  getArrow: function getArrow() {
    
    var _this = this; 
    return function () {
    
        console.log(_this === obj); 
    }; 
  } 
};

13. 扩展运算符的作用及使用场景

  1. 对象的扩展运算符(…)用于取出参数对象中的所有可遍历属性,拷贝到当前对象之中。
let bar = {
    a: 1, b: 2 };
let baz = {
    ...bar }; // { a: 1, b: 2 }

上述方法实际上等价于:

let bar = {
    a: 1, b: 2 };
let baz = Object.assign({
   }, bar); // { a: 1, b: 2 }

Object.assign方法用于对象的合并,将源对象(source)的所有可枚举属性,复制到目标对象(target)。Object.assign方法的第一个参数是目标对象,后面的参数都是源对象。(如果目标对象与源对象有同名属性,或多个源对象有同名属性,则后面的属性会覆盖前面的属性)。

同样,如果用户自定义的属性,放在扩展运算符后面,则扩展运算符内部的同名属性会被覆盖掉。

let bar = {
   a: 1, b: 2};
let baz = {
   ...bar, ...{
   a:2, b: 4
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值