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. 数据类型检测的方式有哪些
(1)typeof
其中数组、对象、null 都会被判断为 object,其他判断都正确。
(2)instanceof
instanceof 可以正确判断对象的类型,其内部运行机制是判断在其
原型链中能否找到该类型的原型。
可以看到,instanceof 只能正确判断引用数据类型,而不能判断基 本数据类型。instanceof 运算符可以用来测试一个对象在其原型链
中是否存在一个构造函数的 prototype 属性。
(3) constructor
constructor 有两个作用,一是判断数据的类型,二是对象实例通过 constr cutor 对象访问它的构造函数。需要注意,如果创建一个对象
来改变它的原型,constructor 就不能用来判断数据类型了:
(4)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 属性是否出现在对象的原型链中的任何位置。
在JavaScript中,instanceof
运算符用于测试一个对象是否在其原型链原型构造函数的 prototype
属性所指向的原型对象上。简而言之,它用来判断一个实例是否属于某个构造函数或类的实例。然而,instanceof
的具体实现是由JavaScript引擎(如V8、SpiderMonkey等)提供的,并且不是直接在ECMAScript规范中定义的算法。不过,我们可以尝试理解其背后的逻辑或自己实现一个类似instanceof
的功能。
背后的逻辑
instanceof
操作符的基本逻辑是:
- 获取右边操作数的
prototype
属性。 - 检查左边操作数的原型链(即
__proto__
链,现代JavaScript中通常通过Object.getPrototypeOf()
访问),看是否存在一个原型对象等于右边操作数的prototype
。 - 如果存在,返回
true
;否则,返回false
。
注意:__proto__
是一个非标准但广泛支持的属性,用于访问对象的原型。但建议使用 Object.getPrototypeOf()
来代替,因为它是一个标准且更安全的方法。
实现类似 instanceof
的函数
下面是一个简单的JavaScript函数,实现了类似 instanceof
的功能
function isInstanceOf(obj, constructor) {
// 1. 获取构造函数的prototype属性
let proto = constructor.prototype;
// 2. 遍历obj的原型链
while (obj !== null) {
// 3. 检查当前原型是否等于构造函数的prototype
if (proto === Object.getPrototypeOf(obj)) {
return true;
}
// 4. 否则,继续向上遍历原型链
obj = Object.getPrototypeOf(obj);
}
// 5. 如果遍历完整个原型链都没有找到,返回false
return false;
}
// 使用示例
function MyClass() {}
const instance = new MyClass();
console.log(isInstanceOf(instance, MyClass)); // true
console.log(isInstanceOf(instance, Object)); // true,因为MyClass的原型链最终指向Object.prototype
console.log(isInstanceOf(instance, Array)); // false
5. 如何获取安全的 undefined 值?
因为 undefined 是一个标识符,所以可以被当作变量来使用和赋值, 但是这样会影响 undefined 的正常判断。表达式 void 没有返 回值,因此返回结果是 undefined。void 并不改变表达式的结果,只是让表达式不返回值。因此可以用 void 0 来获得 undefined。
6. Object.is() 与比较操作符 “=== ”、“== ” 的区别?
使用双等号(==)进行相等判断时,如果两边的类型不一致,则会进行强制类型转化后再进行比较。
使用三等号(===)进行相等判断时,如果两边的类型不一致时,不会做强制类型准换,直接返回 false。
使用 Object.is 来进行相等判断时,一般情况下和三等号的判断相 同,它处理了一些特殊的情况,比如 -0 和 +0 不再相等,两个 NaN
是相等的。
7. 什么是 JavaScript 中的包装类型?
在JavaScript中,包装类型(Wrapper Types)是一种特殊的对象类型,用于将基本数据类型(如字符串String、数字Number和布尔值Boolean)转换为对象。这些包装类型允许在基本数据类型上执行对象操作,因为它们提供了一组方法和属性,以便更轻松地操作基本数据类型的值。以下是关于JavaScript中包装类型的详细解释:
一、包装类型的定义
在JavaScript中,基本类型(如字符串、数字、布尔值)本身并不具有方法和属性。然而,JavaScript提供了三种包装类型:String、Number和Boolean,这些包装类型允许在基本数据类型上调用对象的方法和访问属性。当在基本类型上调用一个方法时,JavaScript会在后台隐式地将基本类型转换为对应的包装类型来执行操作,这个过程被称为“自动包装”(或称为“装箱”Boxing)。操作完成后,JavaScript会自动将包装类型转换回基本类型,这个过程被称为“自动拆箱”(或称为“拆包”Unboxing)。
二、三种主要的包装类型
-
String包装类型:
- 用于字符串的包装类型是String。
- 可以在字符串上调用各种方法,如
length
、charAt()
、substring()
等。 - 示例:
let str = "Hello"; console.log(str.length); // 输出 5
- Number包装类型:
- 用于数字的包装类型是Number。
- 可以在数字上调用各种方法,如
toFixed()
、toPrecision()
等。 - 示例:
let num = 123.456; console.log(num.toFixed(2)); // 输出 "123.46"
- Boolean包装类型:
- 用于布尔值的包装类型是Boolean。
- 可以在布尔值上调用方法,如
toString()
等。 - 示例:
let bool = true; console.log(bool.toString()); // 输出 "true"
三、自动装箱与自动拆箱
- 自动装箱:当你使用基本数据类型的值(如字符串、数字、布尔值)调用对象方法或访问属性时,JavaScript会自动创建对应的包装对象,以便执行操作。
- 自动拆箱:当你完成对包装对象的操作后,JavaScript会自动将其拆箱,将包装对象转换回基本数据类型的值。
四、使用包装类型的注意事项
- 性能开销:自动装箱和自动拆箱会引入额外的性能开销,特别是在大规模循环或高频率的操作中。
- 类型不匹配:由于自动装箱和拆箱,可能会导致类型不匹配的问题。
- 对象引用:当你比较两个对象包装器时,它们的引用而非值会被比较,这可能导致不期望的结果。
- 陷阱在条件语句中:在条件语句中,自动装箱可能导致出乎意料的结果。例如,一个Boolean对象在布尔上下文中总是被视为true,无论它包装的值是true还是false。
五、总结
包装类型是JavaScript中一个重要的概念,它允许在基本类型上调用对象方法和访问属性,从而增强了JavaScript的灵活性和功能性。然而,在使用时需要注意其性能和类型匹配问题,并避免在性能关键的部分频繁进行自动装箱和拆箱操作。在可能的情况下,直接使用基本类型而不是包装类型通常是一个更好的选择。
8.为什么会有BigInt的提案?
JavaScript 中 Number.MAX_SAFE_INTEGER表示最大安全数字,计算结果是9007199254740991,即在这个数范围内不会出现精度丢失(小数除外)。但是一旦超过这个范围,js就会出现计算不准确的情况,这在大数计算的时候不得不依靠一些第三方库进行解决,因此官方提出了BigInt 来解决此问题。
9.如何判断一个对象是空对象
使用JSON自带的.stringtify()方法判断
if(Json.stringtify(obj)=='{}'){
console.log('空对象')
}
使用ES6新增的方法Object.keys()来判读:
if(Object.keys(obj).length<0){
console.log('空对象')
}
10.const对象的属性可以修改吗
const保证的并不是变量的值不能改动,而是变量指向的那个内存地址不能改动。对于基本类型的数据(数值、字符串、布尔值),其值就保存在变量指向的那个内存地址,因此等同于常量。
但对于引用类型的数据(主要是对象和数组)来说,变量指向数据的内存地址,保存的只是一个指针,const只能保证这个指针是固定不变的,至于它指向的数据结构是不是可变的,就完全不能控制了。