1.介绍js的基本数据类型
基本数据类型的数据直接存储在栈(stack)内存中
- String:字符串在 JavaScript 中是不可变的
- Number:JavaScript 中的数字类型是不分整型和浮点型的,所有的数字都是以 64 位浮点数形式存储的。 Number 支持一些特殊的值,如
NaN
(非数字)、Infinity
(无穷大)和-Infinity
(负无穷大)。 - Null:
null
是一个特殊的值,表示一个空对象引用。 - Undefined:当一个变量被声明了但没有被赋值时,它的值就是
undefined
, 表示一个变量存在但是尚未被赋值。 - Boolean:布尔类型包含两个值:
true
和false
。 - Symbol:
Symbol
是一种ES6 新增的原始数据类型,表示独一无二的值。Symbol
类型的值可以作为对象的属性名使用,用于避免属性名冲突。 - BigInt:用来存储超大范围的整数。
2.null,undefined 的区别
一、数据类型
- undefined:undefined表示未定义
- null:null表示空对象
二、使用场景
- undefined:
-
- 变量被声明但没有赋值时,其值为undefined。
- 函数如果没有返回值,则默认返回undefined。
- null:
-
- 作为对象原型链的终点,对象的原型链最终会指向null。
三、转换与比较
- 强转数字时,null会被转换为0,undefined会被转换为NaN(Not-a-Number)。
- null和undefined在宽松比较时是相等的,但在严格比较时则不相等,因为它们的类型不同。
3.JavaScript有⼏种类型的值
JavaScript中的值可以分为两大类:基础数据类型和引用数据类型。
基础数据类型包括:String
Number
Undefined
Null
Boolean
BigInt
Symbol
引用数据类型包括:Object
Function
Array
Date
RegExp
基础数据类型存储在栈内存中
- 栈是一种先进后出的数据结构。
- 栈内存的大小通常是固定的,由系统自动分配和释放,因此访问速度非常快。
引用数据类型存储在堆内存中,
- 堆内存的大小不是固定的,可以根据需要动态扩展。
- 堆内存的访问速度相对较慢,因为需要通过指针来访问堆中的数据。
- 堆内存的管理由JavaScript的垃圾回收机制负责。
4.简单介绍一下 symbol
一、基本概念
- 定义:Symbol是一种基本数据类型,通过Symbol()函数创建的Symbol值都是唯一的
- 作用:Symbol的主要作用是确保对象属性名的唯一性,避免属性名冲突。
二、特点
- 唯一性:Symbol值的唯一性是通过内部机制保证的
- 不可变性:Symbol值一旦创建,就不能被改变或撤销。
- 不可枚举性:Symbol作为对象属性的键时,这些属性不会出现在for...in循环中
- 描述性:Symbol可以接受一个可选的描述参数,用于描述该Symbol的用途。但描述仅用于调试和显示目的,不会影响Symbol的唯一性。
三、使用方式
- 创建Symbol:使用Symbol()函数可以创建一个Symbol值。可以传递一个可选的描述符字符串作为参数,但这不是必须的。
let sym1 = Symbol();
let sym2 = Symbol('description');
console.log(sym1 === sym2); // false
- 作为对象属性名:Symbol可以作为对象属性的键,以确保属性名的唯一性。由于Symbol不是字符串,因此需要使用方括号([])来访问这些属性。
let sym = Symbol('name');
let obj = {
[sym]: 'John'
};
console.log(obj[sym]); // John
- 全局共享的Symbol:使用Symbol.for()方法可以创建或获取一个全局共享的Symbol。如果传入的字符串作为描述符已经存在,则返回该描述符对应的Symbol;如果不存在,则创建一个新的Symbol并添加到全局Symbol注册表中。
let sym1 = Symbol.for('global');
let sym2 = Symbol.for('global');
console.log(sym1 === sym2); // true
- Symbol的内置属性:JavaScript还提供了一些内置的Symbol属性,如Symbol.iterator、Symbol.hasInstance等,用于定义对象的行为。
四、应用场景
- 属性名冲突解决:在多个模块或库之间共享对象时,使用Symbol作为属性名可以避免属性名冲突。
- 定义类的私有属性和方法:由于Symbol的唯一性,可以使用Symbol来定义类的私有属性和方法,从而隐藏类的内部实现细节。
5.深浅拷贝的区别和实现
浅拷贝
浅拷贝试用于基本类型的数据,如果拷贝引用类型的数据, 拷贝之后是引用关系,对于多维数据,只能拷贝第一层数据
方法:
- 使用展开运算符
...
const original = { a: 1, b: { c: 2 } };
const copy = { ...original };
copy.b.c = 3;
console.log(original.b.c); // 输出3,因为b属性是一个对象,浅拷贝只复制了引用
- 使用
Object.assign()
const original = { a: 1, b: { c: 2 } };
const copy = Object.assign({}, original);
copy.b.c = 3;
console.log(original.b.c); // 输出3
深拷贝
深拷贝适用于引用数据类型, 会一个对象,拷贝对象与原来的对象完全分离,对于多维数据要保证多维数据不丢失
方法:
- 使用
JSON.parse()
(音:派尔斯)和JSON.stringify()
(音:斯坠应饭) - 本质是JSON会自己去构建新的内存来存放新对象
const original = { a: 1, b: { c: 2 } };
const copy = JSON.parse(JSON.stringify(original));
copy.b.c = 3;
console.log(original.b.c); // 输出2
- 使用递归
function deepCopy(obj) {
if (typeof obj !== 'object' || obj === null) {
return obj;
}
let copy;
if (Array.isArray(obj)) {
copy = [];
} else {
copy = {};
}
for (let key in obj) {
copy[key] = deepCopy(obj[key]);
}
return copy;
}
const original = { a: 1, b: { c: 2 } };
const copy = deepCopy(original);
copy.b.c = 3;
console.log(original.b.c); // 输出2
- 试用第三方库
lodash 和 jQuery中都有实现深拷贝的方法
6.怎么判断两个对象相等?
判断两个对象是否相等是一个复杂的问题。因为对象本质上是引用类型,所以仅仅比较它们的引用地址是不够的, 还需要比较两个对象的属性和值。
浅比较
浅比较: 对于简单数据类型直接对比值,对于引用数据类型直接对比引用地址
const obj1 = { a: 1, b: 2 };
const obj2 = { a: 1, b: 2 };
const obj3 = obj1;
// 直接比较
console.log(obj1 === obj2); // false 地址不同
console.log(obj1 === obj3); // true 地址相同
// 浅比较 (效果和直接比较类似)
console.log(Object.is(obj1, obj2)); // false
console.log(Object.is(obj1, obj3)); // true
深比较
深比较会递归对象的所有属性和嵌套对象,确保他们完全相同。
第三方库
import { isEqual } from 'lodash';
const obj1 = { a: 1, b: { c: 1 } };
const obj2 = { a: 1, b: { c: 1 } };
console.log(isEqual(obj1, obj2)); // true
自定义深比较函数
function deepEqual(obj1, obj2) {
if (obj1 === obj2) {
return true;
}
if (typeof obj1 != "object" || typeof obj2 != "object" || obj1 == null || obj2 == null) {
return false;
}
let keysA = Object.keys(obj1), keysB = Object.keys(obj2);
if (keysA.length != keysB.length) {
return false;
}
for (let key of keysA) {
if (!keysB.includes(key) || !deepEqual(obj1[key], obj2[key])) {
return false;
}
}
return true;
}
const obj1 = { a: 1, b: { c: 1 } };
const obj2 = { a: 1, b: { c: 1 } };
console.log(deepEqual(obj1, obj2)); // true
JSON比较
把数据转为JSON字符串进行比较
JSON.stringify(obj) == JSON.stringify(obj2); //true
7.JS 判断类型的方法
- typeof 操作符
typeof
是最常用的数据类型判断方法
console.log(typeof "Hello World"); // "string"
console.log(typeof 42); // "number"
console.log(typeof true); // "boolean"
console.log(typeof undefined); // "undefined"
console.log(typeof Symbol()); // "symbol"
console.log(typeof function(){}); // "function"
console.log(typeof {}); // "object"
console.log(typeof []); // "object"
console.log(typeof null); // "object",这是一个历史遗留问题
- instanceof 操作符
instanceof
(音斯特C夫)操作符用于检测构造函数的prototype
属性是否出现在某个实例对象的原型链上。
function Car(make, model) {
this.make = make;
this.model = model;
}
const auto = new Car('Honda', 'Accord');
console.log(auto instanceof Car); // true
console.log(auto instanceof Object); // true
console.log([] instanceof Array); // true
console.log([] instanceof Object); // true
- Object.prototype.toString.call()
这是一个更加准确的方法来判断JavaScript的数据类型
console.log(Object.prototype.toString.call([])); // "[object Array]"
console.log(Object.prototype.toString.call({})); // "[object Object]"
console.log(Object.prototype.toString.call('')); // "[object String]"
console.log(Object.prototype.toString.call(new Date())); // "[object Date]"
console.log(Object.prototype.toString.call(null)); // "[object Null]"
console.log(Object.prototype.toString.call(undefined)); // "[object Undefined]"
8.如何通过JS判断⼀个数组
1. 使用instanceof
操作符
instanceof
操作符用于检测构造函数的prototype
属性是否出现在某个实例对象的原型链上。
const arr = [1, 2, 3];
console.log(arr instanceof Array); // true
const notArr = {};
console.log(notArr instanceof Array); // false
2. 使用Array.isArray()
方法
Array.isArray()
方法返回一个布尔值,表示指定的值是否是一个数组。
const arr = [1, 2, 3];
console.log(Array.isArray(arr)); // true
const notArr = {};
console.log(Array.isArray(notArr)); // false
3. 使用Object.prototype.toString.call()
方法
Object.prototype.toString.call()
方法可以获取一个对象的类型字符串
const arr = [1, 2, 3];
console.log(Object.prototype.toString.call(arr) === '[object Array]'); // true
const notArr = {};
console.log(Object.prototype.toString.call(notArr) === '[object Array]'); // false
9.说一下闭包
一、闭包的定义
- 内层函数引用外层函数的变量就行形成了闭包
二、闭包的作用
- 内存保留:闭包会保留其定义时的作用域链,即使外部函数已经执行完毕,闭包内部依然可以访问外部函数的变量。
- 封装:闭包可以用于封装私有变量,防止其被外部访问和修改。
三、闭包的示例
function outerFunction() {
let outerVariable = '我在outer函数里!';
function innerFunction() {
console.log(outerVariable);
}
return innerFunction;
}
const innerFunc = outerFunction();
innerFunc(); // 输出: 我在outer函数里!
五、可能的问题
- 如果闭包使用不当,可能会导致内存泄露.
10.说说你对作⽤域链的理解
一、定义
作用域链是一个由多个作用域构成的链式结构,它决定了JavaScript引擎在查找变量或函数时的查找顺序。
二、构成
- 全局作用域:在整个代码中都可访问的作用域, 比如<script>和.js文件都属于全局作用域;
- 局部作用域:包括块级作用域和函数作用域,局部作用域声明的变量只在该区域内可以访问;
四、查找规则
- 先从当前执行上下文开始查找。
- 如果能找到,就返回找到的结果。
- 如果未找到,则继续沿着作用域链向上查找,直到全局执行上下文。
- 如果全局执行上下文都没有找到,则返回
undefined
。
五、示例
以下是一个简单的示例,演示了作用域链的工作方式:
var globalVar = "global";
function outerFunction() {
var outerVar = "outer";
function innerFunction() {
var innerVar = "inner";
console.log(innerVar); // 输出 "inner"
console.log(outerVar); // 输出 "outer"
console.log(globalVar); // 输出 "global"
}
innerFunction();
}
outerFunction();
11.那些操作会造成内存泄漏?
内存泄露就是不再使用的内存没有按照预期被释放,导致内存资源浪费
1. 滥用全局变量
- 描述:全局变量会一直存在于内存中,直到页面关闭或被显式地设置为
null
。所以滥用全局变量,很容易导致内存泄漏
2. 闭包
- 描述:闭包可以引用外部函数的变量, 不正确的使用闭包那会导致内存泄漏。
3. 定时器或事件监听器
- 描述:如果定时器(如
setInterval
或setTimeout
)或事件监听器没有被及时清除,它们将一直存在于内存中,并可能继续执行或监听不必要的事件。 - 解决方案:在不再需要定时器或事件监听器时,应及时使用
clearInterval
、clearTimeout
或removeEventListener
等方法进行清理。
4. DOM引用
- 描述:当一个DOM元素被移除或替换后,如果JavaScript代码中仍然保留了对该元素的引用,那么这个元素占用的内存就无法被释放。
- 解决方案:在DOM元素被移除或替换后,应确保删除或更新对该元素的引用。同时,在添加或移除DOM元素时,应注意避免创建不必要的引用关系。
5. 数据缓存
- 描述:如果应用程序中缓存了大量的无用数据,就会导致内存泄漏。
- 解决方案:应定期清理不再需要的数据缓存,并设置合理的缓存策略以避免缓存过多无用的数据。
12.Javascript全局函数和全局变量
全局变量
全局变量是在全局作用域中声明的变量,这意呀着它们在程序的任何地方都可以被访问和修改。但是,过度使用全局变量可能会导致命名冲突和难以维护的代码。
// 声明一个全局变量
var globalVar = "我是一个全局变量";
function testGlobal() {
console.log(globalVar); // 可以访问全局变量
}
testGlobal(); // 输出: 我是一个全局变量
全局函数
全局函数是在全局作用域中定义的函数,因此可以在程序的任何地方调用它们。
// 调用内置的全局函数
console.log(parseInt("100", 10)); // 输出: 100
// 自定义全局函数
function globalFunction() {
console.log("这是一个全局函数");
}
globalFunction(); // 调用自定义全局函数
注意事项
- 避免使用全局变量和全局函数:全局变量和全局函数可能导致命名冲突,特别是在大型项目中。尽量使用局部变量和函数,并通过模块、闭包等方式封装代码。
- 严格模式:在JavaScript的严格模式(strict mode)下,未声明的变量尝试赋值时会保存,这有助于减少意外创建全局变量的风险。
- 全局对象的属性:在浏览器环境中,全局变量实际上是
window
对象的属性,而在Node.js中,它们是global
对象的属性。
示例:使用严格模式
"use strict";
// 尝试在未声明的情况下赋值会导致错误
// var x = 10; // 移除这行代码
x = 10; // 抛出ReferenceError: x is not defined
function testStrict() {
"use strict";
// 这里的代码也会遵循严格模式
}
13.JS 的作用域类型
JavaScript 通过作用域控制程序中变量、函数和对象的可访问性。JavaScript 中主要有两种作用域类型:全局作用域和局部作用域,其中局部作用域又可以细分为函数作用域和块级作用域。
1. 全局作用域(Global Scope)
全局作用域是最外层的作用域, <script></script>标签.js文件的外层就属于全局作用域。在全局作用域中声明的变量和函数可以在整个程序中访问
var globalVar = "我是全局变量";
function globalFunction() {
console.log("我是全局函数");
}
console.log(globalVar); // 可以访问
globalFunction(); // 可以调用
2. 局部作用域(Local Scope)
局部作用域限制在函数或块级作用域内部。在局部作用域中声明的变量和函数只能在定义它们的作用域内部访问。
2.1 函数作用域(Function Scope)
在 ES6 之前,JavaScript 只有函数作用域。这意味着变量和函数在函数内部声明时,其作用域限制在该函数内部。
function funcScopeExample() {
var localVar = "我是局部变量";
function localFunction() {
console.log("我是局部函数");
}
console.log(localVar); // 可以访问
localFunction(); // 可以调用
}
// console.log(localVar); // ReferenceError: localVar is not defined
// localFunction(); // ReferenceError: localFunction is not defined
2.2 块级作用域(Block Scope)
ES6 引入了 let
和 const
关键字,它们提供了块级作用域。这意味着变量或常量可以在代码块内部声明,并且其作用域限制在该代码块内部。
if (true) {
let blockVar = "我是块级变量";
console.log(blockVar); // 可以访问
}
// console.log(blockVar); // ReferenceError: blockVar is not defined
for (let i = 0; i < 5; i++) {
// ...
}
// console.log(i); // ReferenceError: i is not defined
标签:面试题,console,log,作用域,Symbol,JS,精选,const,函数
From: https://blog.csdn.net/CSDN20221005/article/details/141497872