前端面试准备学习记录 — JS篇(持续更新中)

三、JS

一、JS数据类型

1.1、JS数据类型

八种类型:undefined、null、boolean、number、string、object、symbol(ES6)、bigint(ES6)

栈:基本数据类型(undefined、null、boolean、number、string)开辟新内存

堆:引用类型数组(对象、数组、函数)浅拷贝,内存地址不变

两者存储位置不同:

基本数据类型:存储在栈中,占据空间小,大小固定,属于被频繁使用的数据,所以放入栈中存储

引用数据类型:存储在堆中,占据空间小,大小不固定,在栈中存储了指针,指向堆中该实体的起始地址,当寻找引用值时,回首先检索其在栈中的地址,然后从堆中获得实体

1.2、数据类型检验方法

  1. 使用typeof,返回该数据类型

    console.log(typeof 2)
    
  2. 使用instanceof,返回true或false

    console.log(2 instanceof Number)
    
  3. constructor,返回true或false;可以通过以下方法判断数据类型,也可以对象实例通过constrcutor对象访问他的构造函数

    console.log((2).constructor === Number)
    
  4. Object.prototype.toString.call()

    Object.prototype.toString.call()使用Object对象的原型方法toString来判断数据类型

    var a = Object.prototype.toString;
    console.log(a.call(2))
    

1.3、判断数组方式

  1. Object.prototype.toString.call()

  2. 原型链

    obj._proto_ === Array.prototype
    
  3. ES6的Array.isArray()判断

    Array.isArray(obj)
    
  4. instanceof判断

    obj instanceof Array
    
  5. Array.prototype.isPrototypeOf()

    Array.prototype.isPrototypeOf(obj)
    

1.4、null和undefined区别

都是基本数据类型,都只有一个值,即ubdefined和null;前者代表未定义,后者代表空对象;变量声明了但没有定义返回undefined,null赋值给一些可能会返回对象的变量,作为初始化。

1.5、intanceof操作符的实现原理及实现

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

function myInstanceof(left, right) {
    // 获取对象原型
    let proto = Object.getPrototypeOf(left)
    // 获取构造函数的prototype对象
    let prototype = right.prototype
    // 判断构造函数的prototype对象是否在对象的原型链上
    while (true) {
        if (!proto) return false;
        if (proto === prototype) return true
        // 如果没有找到,就继续从其原型链上找,Object.getPrototypeOf方法来获取指定对象的原型
        proto = Object.getPrototypeOf(proto)
    }
}

1.6、0.1+0.2 != 0.3问题

let a = 0.1,b = 0.2
console.log(a+b);  //0.30000000000000004
console.log((a+b).toFixed(2));  //0.30四舍五入两位

出现原因:计算机存储数据时,使用二进制,这两个数转换为二进制都是无限循环的数,相加就会出现误差

如何解决:一种就是上边的四舍五入;另一种就是利用ES6中的Number.EPSILON(2的负52次方),如果两数之和小于Number.EPSILON,就判断0.1+0.2 === 0.3

function numberepsilon(a,b) {
    return Math.abs(a - b) < Number.EPSILON
}
console.log(numberepsilon(0.1+0.2,0.3));  //true

1.7、isNaN与Number.isNaN函数的区别

isNaN:接收参数后,会尝试将参数转换为数值,不能被转换为数值的值都会返回true,非数字传入也会返回true

Number.isNaN:先判断是不是数字,是数字再进行判断是否NaN,不会进行数据类型的转换,此方法判断更为准确

1.8、Object.is()与===、==的区别

  1. ==:两边类型不一致会进行强制转换后再进行比较
  2. ===:两边类型不一致,直接返回false
  3. 一般与===相同,处理了一些特殊情况,比如-0与+0不再相等,两个NaN是相等的

1.9、JS隐式类型转换

ToPrimitive方法:用来将值(基本类型、对象)转换为基本类型值。值为基本类型返回其本身

基本类型的隐式转换

1.10、object.assign与扩展运算符

let outobj = {
    inobj:{a:1,b:2}
}

// 扩展运算符
let newobj = {...outobj}
newobj.inobj.a = 2
console.log(outobj);  //{ inobj: { a: 2, b: 2 } }

// Object.assign()
let newobj2 = Object.assign({},outobj)
newobj2.inobj.a = 2
console.log(outobj);  //{ inobj: { a: 2, b: 2 } }

相同点:两者都是浅拷贝

不同点:

扩展运算符:数组或对象中的每一个值都会被拷贝到一个新的数组或对象中,会复制ES6的symbol属性

Object.assign():接受第一个参数为目标对象,后面的所有参数作为源对象,然后把所有的源对象合并到目标对象中,会触发ES6 setter

1.11、其他

  1. typeof null的结果是Object

  2. typeof NaN的结果是number

  3. JS的包装类型:基本类型没有属性和方法,为了便于操作,在调用时JS会在后台隐式的将基本类型转换为对象进行操作

  4. BigInt出现原因:为了解决超出最大精度后计算不准确的问题

  5. 判断是否为空对象

    1. JSON自带的.stringify

      console.log(JOSN.stringify(obj) == '{}')
      
    2. ES6新增的Object.keys()

      console.log(object.keys(obj).length < 0)
      

二、JS基础

2.1、new操作符实现原理

  1. 首先创建一个新的空对象
  2. 设置原型,将对象的原型设置为函数的prototype对象
  3. 让函数的this指向这个对象,执行构造函数的代码(为这个新对象添加属性)
  4. 判断函数的返回值类型,如果是指类型,就返回创建的对象。如果是引用类型,就返回这个引用类型的对象

总结:创建一个新对象,设置对象的原型链,将构造函数的作用域赋给新对象,返回新对象

function a() {
    let newObj = null;
    let constructor = Array.prototype.shift.call(arguments)
    let result = null
    // 判断参数是否是一个函数
    if(typeof constructor !== 'function'){
        console.error('type error');
        return
    }
    // 新建一个空对象,对象的原型为构造函数的prototype对象
    newObj = Object.create(constructor.prototype)
    // 将this指向新建对象,并执行函数
    result = constructor.apply(newObj,arguments)
    // 判断返回对象
    let flag = result && (typeof result ==='object' || typeof result === 'function')
    // 判断返回结果
    return flag ? result : newObj
}
// 使用方法
a(构造函数,初始化函数)


//具体实现
function Person(name, age) {
  this.name = name;
  this.age = age;
}

// 使用 new 操作符创建一个新的 Person 实例
const person1 = new Person('Alice', 25);
console.log(person1.name); // 输出: Alice
console.log(person1.age);  // 输出: 25

2.2、map与object、map与weakmap的区别

2.3、数组相关

类数组对象:一个拥有length属性和若干索引属性的对象就可以被称为类数组对象,类数组对象和数组相似,但是不能调用数组的方法。常见的类数组对象有arguments和DOM方法的返回结果,一个函数也可以被看做,因为它含有length属性值,代表可以接收的参数个数

//类数组转换为数组的方法
Array.prototype.slice.call(arrayLike)  //通过call调用数组的slice方法
Array.prototype.slice.call(arrayLike,0)
Array.prototype.slice.call([],arrayLike)  //通过apply调用数组的concat方法
Array.from(arrayLike)  //通过Array.from实现

数组的原生方法:

  1. 数组转换字符串:toString()、toLocaleString()、join(*),最后一个可以制定转换为字符串时的分隔符

  2. 尾部操作:弹出尾部元素pop()、尾部添加元素push(a)

  3. 首部操作:删除首部shift()、首部插入unshift(a)

  4. 重排序:反转数组reverse()、默认从小到大sort()

    let a = [1, 2, 3, 4, 5, 0.1]
    
    a.sort(function (a, b){ return b - a })  //[ 5, 4, 3, 2, 1, 0.1 ]
    a.sort(function (a, b){ return a - b })  //[ 0.1, 1, 2, 3, 4, 5 ]
    
  5. 数组连接:concat()

    let a = [1, 2, 3, 4, 5]
    let b = [6, 7, 8, 9, 10]
    
    let c = a.concat(b)
    console.log(c);  // [1, 2, 3, 4, 5 , 6, 7, 8, 9, 10]
    
  6. 数组截取:slice()

    let a = [1, 2, 3, 4, 5]
    
    console.log(a.slice(2));  // 倒数三个 [ 3, 4, 5 ]
    console.log(a.slice(2,4));  // 左开右闭 [ 3, 4 ]
    console.log(a.slice(1,5));  // 超出范围 [ 2, 3, 4, 5 ]
    console.log(a.slice(-2));  // 倒数两个 [ 4, 5 ]
    console.log(a.slice(2,-1));  // 取两个,从倒数第二个位置开始 [ 4, 5 ]
    console.log(a.slice());  // 全部读取
    
  7. 数组插入&&删除&&替换:splice(startIndex, deleteCount, item1, item2, …)

    const array = [1, 2, 3, 4, 5];
    
    array.splice(2, 1); // 从索引 2 开始删除 1 个元素
    console.log(array); // 输出: [1, 2, 4, 5]
    
    array.splice(2, 0, 6); // 在索引 2 处插入元素 6,不删除任何元素
    console.log(array); // 输出: [1, 2, 6, 3, 4, 5]
    
    array.splice(2, 1, 6); // 从索引 2 处删除 1 个元素,并插入元素 6
    console.log(array); // 输出: [1, 2, 6, 4, 5]
    
    array.splice(1, 2, 6, 7, 8); // 从索引 1 处删除 2 个元素,并插入元素 6, 7, 8
    console.log(array); // 输出: [1, 6, 7, 8, 4, 5]
    
    array.splice(1, 3); // 从索引 1 处删除 3 个元素
    console.log(array); // 输出: [1, 5]
    
    array.splice(array.length, 0, 4); // 在数组末尾插入元素 4
    console.log(array); // 输出: [1, 2, 3, 4]
    
    // indexOf()方法
    console.log(arr.indexOf(1));  //数组中有该元素,返回0,否则返回-1
    
    // every()方法
    console.log(arr.every(e => e > 0));  //true 数组中所有元素满足条件返回true,否则返回false
    
    // some()方法
    //用于检查数组中是否至少有一个元素满足指定的条件。这个方法会遍历数组的每个元素,直到找到一个满足条件的元素,如果找到了就返回 true,否则返回 false
    console.log(arr.some(e => e % 2 == 0));  // true
    
    // filter()方法
    // 用于创建一个新数组,其中包含满足指定条件的原数组元素
    console.log(arr.filter(e => e > 3));  //[ 4, 5 ]
    
    // map()方法
    // 用于创建一个新数组,该数组的元素是原始数组经过某个函数处理后的结果
    console.log(arr.map(e => e * 2));  // [ 2, 4, 6, 8, 10 ]
    
    // forEach()方法
    // 用于遍历数组的每个元素,并对每个元素执行指定的操作
    arr.forEach(function (e, index){
        console.log(e);
    });  //1 2 3 4 5
    
  8. 数组归并:reduce()对数组中的所有元素执行一个累加操作,并返回最终的累积结果

const numbers = [1, 2, 3, 4, 5];

// accumulator:累积的结果,它是每次回调执行后的返回值,也可以在回调函数中被更新;currentValue:当前正在处理的数组元素。
const sum = numbers.reduce(function(accumulator, currentValue) {
  return accumulator + currentValue;
}, 0); // 初始值为 0
console.log(sum); // 输出 15,因为 1 + 2 + 3 + 4 + 5 = 15

2.4、类数组

arguments是一个对象,是从0开始依次递增的数字,有callee和length属性,与数组相似

遍历类数组方法:

// 将数组的方法用到类数组上
function foo(){
	Array.prototype.forEach.call(arguments,a => console.log(a))
}
// 使用Array.from
function foo(){
	const arrArgs = Array.from(arguments)
	arrArgs.forEach(a => console.log(a))
}
// 使用展开运算符将类数组转化为数组
function foo(){
	cosnt arrArgs = [...arguments]
	arrArgs.forEach(a => console.log(a))
}

2.5、DOM和BOM

DOM(文档对象模型)和BOM(浏览器对象模型)用于与网页文档和浏览器进行交互

DOM:

  • DOM 是一种用于表示和操作网页文档结构的模型。

  • 它将网页文档表示为一个树状结构,其中每个元素(如 HTML 标签)都是树的节点,可以通过 JavaScript 访问和操作。

  • DOM 提供了一组API,允许开发者通过JavaScript来读取、修改、添加和删除网页中的元素和内容。

  • DOM 的核心对象是document,它代表整个网页文档,包含了HTML、CSS和JavaScript等内容的访问接口。

    // 可以使用以下代码获取一个元素的引用并修改其内容
    var element = document.getElementById("myElement");
    element.innerHTML = "新内容";
    

    BOM:

    • BOM 是一种用于与浏览器窗口和浏览器本身进行交互的模型。
    • BOM 提供了一组对象,允许开发者访问浏览器窗口的属性和方法,以及与浏览器的交互,如导航、弹出窗口、处理 cookies 等。
    • BOM 的核心对象是window,既是通过JS方位浏览器窗口的一个接口,又是一个Global(全局)对象。
    // 可以使用以下代码在浏览器中打开一个新窗口
    window.open("https://www.example.com", "_blank");
    

    总结:

    • DOM 用于访问和操作网页文档的内容和结构。
    • BOM 用于与浏览器窗口和浏览器本身进行交互。
    • 这两者都是 JavaScript 提供的对象模型,使开发者能够创建交互性更强的网页和应用程序。
    • DOM的最根本对象document对象也是BOM的window对象的子对象

2.6、AJAX

是通过JS的异步通信,从服务器获取数据再更新当前页面的对应部分,而不需要刷新真个网页

创建AJAX请求步骤:

  1. 创建 XMLHttpRequest 对象
  2. 设置请求参数(如 URL、HTTP 方法、请求头、请求体等)
  3. 定义响应处理参数,处理请求成功和失败的情况
  4. 发送请求
var xhr = new XMLHttpRequest();

xhr.open(method, url, async);
xhr.setRequestHeader(header, value);

xhr.onload = function() {
  if (xhr.status === 200) {
    // 请求成功,处理响应数据
  } else {
    // 请求失败,处理错误
  }
};
xhr.onerror = function() {
  // 处理请求错误
};

2.7、JS变量提升

会将变量核函数的生命提升到他们所在作用域的顶部,然后再执行代码

进行变量提升的步骤:

  1. JavaScript 解释器在执行代码之前会对代码进行两次扫描。第一次扫描会找到所有的变量声明(使用 varletconst 声明变量)以及函数声明(使用 function 关键字声明函数)
  2. 这些声明会被提升到当前作用域的顶部,但是它们的赋值(如果有的话)不会提升,赋值操作仍然在原始位置执行

好处:

  1. 提高性能:JS执行代码之前,会进行语法检查和预编译,该操作只会执行一次,让函数可以在执行时预先为变量分配栈空间
  2. 容错性更好:是一些不规范的代码也可以正常运行

问题:

  1. let、const定义的变量没有变量提升的机制
  2. 变量泄露:变量在其实际声明之前被访问,这可能导致未定义的行为或变量泄漏到不应该访问它们的作用域
  3. 可读性差:不同作用域内的变量和函数在声明之前就可以被访问
  4. 意外覆盖:在使用 var 声明变量时,因为它们在整个函数作用域内都可见,可能导致变量被意外覆盖

2.8、ES6模块与CommonJS模块

不同点:

  1. ES6是对模块的引用,指针指向不变;存和读数据;使用import、export导入导出数据;异步加载;浏览器端的主流选择
  2. CommonJS是对模块的浅拷贝;使用require()、module.exports导入导出;同步加载;大多应用于服务端(如NodeJS)

相同点:

  1. 都是用于模块化 JavaScript 代码的工具
  2. 都可以对引入的对象进行赋值,即对对象内部属性的值进行改变

2.9、常见的DOM操作

  1. 节点的获取

    document.getElementById(id)  // 通过元素的 id 属性选取一个元素。
    document.getElementsByClassName(className)  // 通过元素的类名选取一个或多个元素。
    document.getElementsByTagName(tagName)  // 通过元素的标签名选取一个或多个元素。
    document.querySelector(selector)  // 通过 CSS 选择器选取匹配的第一个元素。
    document.querySelectorAll(selector)  // 通过 CSS 选择器选取所有匹配的元素。
    
  2. 节点的创建

    document.createElement(tagName)  // 创建一个新的元素节点。
    document.createTextNode(text)  // 创建一个包含文本内容的文本节点。
    document.createDocumentFragment()  // 创建一个文档片段,用于高效地批量添加和操作元素。
    
  3. 元素插入和删除

    element.appendChild(childElement)  // 将一个子元素添加到父元素的末尾。
    element.insertBefore(newElement, referenceElement)  // 在参考元素之前插入一个新元素。
    element.removeChild(childElement)  // 从父元素中移除一个子元素。
    element.replaceChild(newElement, oldElement)  // 替换一个子元素。
    element.cloneNode(deep)  // 克隆一个元素节点。
    

2.10、for…in与for…of的区别

  1. for…in:ES3,主要遍历对象,获取对象键名;会遍历对象的整个原型链;返回数组所有可枚举的属性
  2. for…of:ES6,获取对象的键值;只遍历当前对象;只返回数组对应的属性
  3. 使用for…of遍历对象的方法:用Array.from转为数组

2.11、数组遍历方法

  1. 使用标准的 for 循环可以遍历数组的每个元素
  2. forEach 方法是数组的内置方法,用于遍历数组的每个元素并执行指定的回调函数
  3. map 方法也是数组的内置方法,它用于遍历数组的每个元素,并返回一个新的数组,新数组的每个元素都是原数组经过回调函数处理后的结果
  4. for...of 循环是一种现代 JavaScript 的遍历数组的方法,它可以遍历数组的值而不需要索引
  5. for...in 循环用于遍历对象的属性,但也可以用于遍历数组的索引。不过需要小心使用,因为它可能会遍历到数组的原型属性
  6. reduce 方法用于遍历数组的每个元素,并将它们累积到一个单一的值中
  7. filter 方法: filter 方法用于遍历数组的每个元素,并返回一个包含符合条件的元素的新数组
var arr = [1, 2, 3, 4, 5];

// for循环
for (var i = 0; i < arr.length; i++) {
    console.log(arr[i]);
}

// forEach
arr.forEach(function (item) {
    console.log(item);
});

// map
var newArr = arr.map(function (item) {
    console.log(item * 2);
});

// for...of
for (var item of arr) {
    console.log(item);
}

// for...in
for (var index in arr) {
    console.log(arr[index]);
}

// reduce
var sum = arr.reduce(function (acc, item) {
    return acc + item;
}, 0)
console.log(sum);

// filter
var evenNumbers = arr.filter(function(item) {
    return item % 2 === 0;
});
console.log(evenNumbers);

2.12、其他

  1. JS内置对象主要指的是在程序执行前存在全局作用域里的由JS定义的一些全局值属性、函数和用来实例化其他对象的构造函数。一般常用的全局变量有NaN、underfined,全局函数有parseint()、parseFloat()等

  2. JSON是一种基于文本的轻量级的数据交换格式,可以被任何的编程语言读取和作为数据格式来传递。在JS中提供了两个函数来实现。JSON.stringify(),将符合JSON格式的数据转换为JSON字符串;JSON.paras()将JSON类型的字符串转换为JS数据结构

  3. JS脚本延迟加载方法

    1. defer:文档解析完成后执行此脚本,多个含defer的脚本,按顺序执行
    2. asaync:脚本异步加载,执行顺序不可预测
    3. 动态创建DOM:动态添加DOM标签,对文档的加载事件进行监听,当文档加载完成后再动态的创建script标签来引入js脚本
    4. 使用setTimeout延迟方法
    5. 将JS放在文档的底部
  4. Unicode、UTF-8、UTF-16、UTF-32的区别:前者是编码字符集(字符集)、后三者是字符集编码(编码规则)

  5. 原码、反码、补码:原码就是一个数的二进制数;反码:正数的反码与原码相同,负数的反码为除去符号位,按位取反;补码:正数的相同,负数的为原码除符号位外所有为取反,然后加1

  6. 尾调用:发生在函数的最后一条语句,调用结果直接返回给函数的调用者,而不需要进行额外的计算或操作,有助于优化递归算法,减少内存消耗。

    function factorial(n, accumulator = 1) {
      if (n === 0) {
        return accumulator;
      }
      return factorial(n - 1, n * accumulator); // 尾调用
    }
    
    console.log(factorial(5)); // 输出 120
    
  7. 强类型语言(Java、C++)与弱类型语言(JS)的区别:前者严谨,可以高效的避免一些错误;后者编译速度快

  8. 解释性语言(JS、Python)和编译性语言(C++)的区别:前者编译后即可在该平台运行,后者是在编译期间才编译;前者速度快,后者跨平台性好

  9. ajax、axios、fetch:

    1. ajax:使网页实现异步更新,不重新加载整个页面的情况下,对网页某部分进行更新
    2. axios:基于promise封装的HTTP客户端
    3. fetch:基于promise设计的
  10. addEventListener方法

    // element:要添加事件监听器的 HTML 元素。
    // eventType:要监听的事件类型,通常是一个字符串,例如 "click", "keydown", "mousemove" 等。
    // callbackFunction:事件触发时要执行的回调函数。
    // useCapture(可选参数):一个布尔值,用于指定事件是在捕获阶段(true)还是冒泡阶段(false)处理。大多数情况下,可以忽略此参数,使用默认值 false。
    
    element.addEventListener(eventType, callbackFunction[, useCapture]);
    
    // 案例
    // 获取按钮元素
    var button = document.getElementById("myButton");
    
    // 添加点击事件监听器
    button.addEventListener("click", function() {
        alert("按钮被点击了!");
    });
    

你可能感兴趣的:(前端,面试,学习)