货拉拉 前端开发 提前批一面面经
校招面试大多都是基础题目,大约一小时
1、数组的方法
2、扁平化和数组去重
3、foreach和map的区别
4、类型判断的方法
5、 基本类型和引用数据类型的区别
6、深拷贝方法,如何实现
7、什么时候用JSON.stringify()
8、如何实现递归,数组扁平化中的递归的缺陷
9、 this指向
10、== 和 === 的区别
11、原型、原型链
12、创建对象方法
13、promise,async、await
14、模块化CommonJS、AMD、CMD、UMD、ES6 Module有什么区别
15、什么是事件循环?
16、宏任务微任务?
17、 隐藏元素的方法
18、BFC,实现方法
19、get、post区别
20、 状态码 301,401,501
21、浏览器缓存机制,强缓存、协商缓存区别
22、 同源策略,跨域解决方法
23、 React中 useState() 是同步还是异步
24、 Vue生命周期
25、 v-if和v-show
26、 v-for为什么要有key
修改原数组的方法:这些方法会直接修改原始数组,包括 push()、pop()、shift()、unshift()、splice()、sort()、reverse()。
不修改原数组的方法:这些方法会返回一个新的数组或其他数据类型,而不会修改原数组,包括 concat()、slice()、join()、toString()、indexOf()、lastIndexOf()、includes()、some()、every()、filter()、map()、reduce()、reduceRight()、find()、findIndex()、flat()等。
数组扁平化可以使用 flat
方法
flat(Infinity)方法用于将多维数组转换为一维数组。如果不知道嵌套层级,可以使用Infinity作为参数。
const arrayToFlatten = [1, 2, [3, 4], [5, [6, 7]]];
const flattenedArray = arrayToFlatten.flat(Infinity);
console.log(flattenedArray);
或者使用递归实现数组扁平化:
function flattenArray(arr) {
let flattened = [];
for (let i = 0; i < arr.length; i++) {
if (Array.isArray(arr[i])) {
flattened = flattened.concat(flattenArray(arr[i]));
} else {
flattened.push(arr[i]);
}
}
return flattened;
}
const nestedArray = [1, 2, [3, 4], [5, [6, 7]]];
const flattenedArray = flattenArray(nestedArray);
console.log(flattenedArray);
数组去重是指从数组中删除重复的元素:
const arrayToDeduplicate = [1, 2, 2, 3, 4, 4, 5];
const deduplicatedArray = Array.from(new Set(arrayToDeduplicate));
console.log(deduplicatedArray);
通过new Set()构造函数创建一个集合,利用集合的特性自动去除重复元素,然后使用Array.from()方法将集合转换回数组。
forEach是用于遍历数组的方法,它接受一个回调函数作为参数,并对数组中的每个元素执行该回调函数。forEach方法不会返回新的数组,而是对原数组进行操作或执行一些操作,比如打印元素、修改元素等。
const array = [1, 2, 3];
array.forEach((element) => {
console.log(element);
});
map方法也是用于遍历数组的方法,但它会返回一个新的数组,新数组的每个元素由原数组经过回调函数处理得到。
const array = [1, 2, 3];
const doubledArray = array.map((element) => {
return element * 2;
});
console.log(doubledArray); // 输出: [2, 4, 6]
用于检测给定值的数据类型,并返回一个字符串表示该类型:
typeof 42; // 返回 "number"
typeof "hello"; // 返回 "string"
typeof true; // 返回 "boolean"
typeof []; // 返回 "object"
typeof {}; // 返回 "object"
typeof null; // 返回 "object"
typeof undefined; // 返回 "undefined"
typeof function() {}; // 返回 "function"
对于原始数据类型,我们可以使用typeof()函数来判断他的数据类型
typeof并不总是能够准确地区分对象类型,对于数组和null而言,它会返回"object"。为了更准确地判断是否为数组,可以使用Array.isArray()方法。
typeof 运算符对于 null 值会返回 "object"。这实际上是 JavaScript 最初实现中的一个错误,然后被 ECMAScript 沿用了。现在,null 被认为是对象的占位符,从而解释了这一矛盾,但从技术上来说,它仍然是原始值。
instanceof 用来判断一个变量是否是某个对象的实例,所以对于引用类型我们使用instanceof来进行类型判断
const arr = [];
arr instanceof Array; // 返回 true
const obj = {};
obj instanceof Object; // 返回 true
const date = new Date();
date instanceof Date; // 返回 true
Object.prototype.toString.call() 方法:用于返回对象的字符串表示,可以用来判断对象的类型
Object.prototype.toString.call(42); // 返回 "[object Number]"
Object.prototype.toString.call("hello"); // 返回 "[object String]"
Object.prototype.toString.call(true); // 返回 "[object Boolean]"
Object.prototype.toString.call([]); // 返回 "[object Array]"
Object.prototype.toString.call({}); // 返回 "[object Object]"
Object.prototype.toString.call(null); // 返回 "[object Null]"
Object.prototype.toString.call(undefined); // 返回 "[object Undefined]"
Object.prototype.toString.call(function() {}); // 返回 "[object Function]"
isNaN() 函数:用于检测一个值是否是 NaN。注意,它会先尝试将参数转换为数字类型,然后再进行判断
isNaN(NaN); // 返回 true
isNaN(42); // 返回 false
isNaN("hello"); // 返回 true
基本类型(也称为原始类型)包括以下几种:
基本类型的特点:
引用类型的特点:
基本类型是存储简单数据值的数据类型,而引用类型是存储对象或复杂数据结构的数据类型。对于引用类型,可以通过原型继承来共享属性和方法,而基本类型则没有原型。
浅拷贝:将对象的引用复制给一个新对象,新对象和原对象引用的是同一个对象,修改一个对象的属性会影响另一个对象的属性。常见的浅拷贝方法有Object.assign()、扩展运算符(...)等。
深拷贝:将对象完全复制一份,新对象和原对象是两个独立的对象,修改一个对象的属性不会影响另一个对象的属性。常见的深拷贝方法有递归拷贝、JSON.stringify和JSON.parse
function deepCopy(source) {
if (typeof source !== 'object' || source === null) {
return source;
}
const target = Array.isArray(source) ? [] : {};
for (const key in source) {
if (Object.prototype.hasOwnProperty.call(source, key)) {
target[key] = deepCopy(source[key]);
}
}
return target;
}
JSON.stringify() 方法用于将 JavaScript 对象转换为 JSON 字符串
JSON.stringify() 只能处理那些可以被有效转换为 JSON 格式的值,例如字符串、数字、布尔值、数组、对象字面量、以及可以被转换为这些类型的值。对于函数、正则表达式、循环引用等特殊情况,JSON.stringify() 会进行相应的处理(例如忽略函数和正则表达式,或抛出错误)。
数组扁平化中使用递归的一个潜在缺陷是可能会导致栈溢出。当处理非常大的嵌套数组时,递归调用会不断增加调用栈的深度,最终可能导致栈溢出错误。
使用递归的扁平化方法也可能会影响性能,因为递归调用本身会产生一定的开销,尤其是在处理大型数组时。
为了解决这些问题,可以考虑使用迭代(iteration)而不是递归来实现数组扁平化:
function flattenArray(arr) {
const flattened = [];
const stack = [...arr];
while (stack.length) {
const next = stack.pop();
if (Array.isArray(next)) {
stack.push(...next);
} else {
flattened.unshift(next);
}
}
return flattened;
}
const nestedArray = [1, 2, [3, 4], [5, [6, 7]]];
const flattenedArray = flattenArray(nestedArray);
console.log(flattenedArray);
当在全局作用域中使用 this 时,它将指向全局对象,在浏览器中通常是 window 对象。
function sayHello() {
console.log(this);
}
sayHello(); // 在浏览器中,指向 window 对象
对象方法中,this 指向调用该方法的对象
const person = {
name: "Alice",
greet: function() {
console.log("Hello, my name is " + this.name);
}
};
person.greet(); // this 指向 person 对象
在构造函数中,this 指向通过 new 关键字创建的实例对象。
function Person(name) {
this.name = name;
}
const alice = new Person("Alice");
console.log(alice.name); // Alice
在事件处理函数中,this 通常指向触发事件的元素。
document.getElementById("myButton").addEventListener("click", function() {
console.log(this); // 指向触发点击事件的按钮元素
});
箭头函数不会改变 this 的指向,它会继承外层作用域的 this 值。因此,在箭头函数中,this 的指向由箭头函数定义时的外部环境决定,而不是调用时的情况。
== 和 === 都是用于比较两个值的运算符,它们之间的区别在于严格性和类型转换。
双等号用于比较两个值时会进行类型转换,如果类型不同,JavaScript 会尝试将它们转换为相同类型再进行比较。
三个等号比较时不会进行类型转换,只有在类型相同且值也相等的情况下才会返回 true。
每个对象都有一个原型(prototype),而原型本身也是一个对象。原型链(prototype chain)是一种机制,用于实现对象之间的继承关系。
原型:每个函数都有一个 prototype(原型) 属性,这个属性是一个指针,指向一个对象,而这个对象包含某种特定类型的所有实例共享的属性和方法。
原型链:JavaScript 中所有的对象都是由它的原型对象继承而来。而原型对象自身也是一个对象,它也有自己的原型对象,这样层层上溯,就形成了一个类似链表的结构,这就是原型链
当试图访问一个对象的属性或方法时,如果该对象本身没有这个属性或方法,JavaScript 引擎会沿着原型链向上查找,直到找到对应的属性或方法,或者到达原型链的顶端(即 Object.prototype)为止。
使用对象字面量可以直接创建一个对象,并为其添加属性和方法。
const person = {
name: "John",
age: 30,
sayHello: function() {
console.log("Hello, my name is " + this.name);
}
};
console.log(person.name); // 访问属性
person.sayHello(); // 调用方法
使用构造函数可以创建一个对象模板,并通过 new 关键字实例化多个对象。
function Person(name, age) {
this.name = name;
this.age = age;
this.sayHello = function() {
console.log("Hello, my name is " + this.name);
};
}
const person1 = new Person("John", 30);
const person2 = new Person("Jane", 25);
console.log(person1.name); // 访问属性
person2.sayHello(); // 调用方法
使用 Object.create() 方法可以基于指定的原型创建一个新对象。
const personProto = {
sayHello: function() {
console.log("Hello, my name is " + this.name);
}
};
const person = Object.create(personProto);
person.name = "John";
console.log(person.name); // 访问属性
person.sayHello(); // 调用方法
ES6 引入了类(class)语法,通过 class 关键字可以定义一个类,并使用 new 关键字实例化对象
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
sayHello() {
console.log("Hello, my name is " + this.name);
}
}
const person1 = new Person("John", 30);
const person2 = new Person("Jane", 25);
console.log(person1.name); // 访问属性
person2.sayHello(); // 调用方法
Promise 是一种处理异步操作的方式,可以在异步操作完成后执行相应的操作。async 和 await 则是基于 Promise 的语法糖,让异步操作更加简单和易读。
使用 Promise 可以将一个异步操作封装成一个对象,通过 then() 方法注册回调函数,在异步操作完成后调用回调函数。
function asyncOperation() {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve("Operation completed successfully!");
}, 1000);
});
}
asyncOperation().then(result => {
console.log(result);
}).catch(error => {
console.log(error);
});
async 和 await 是 ES7 引入的新特性,它们使得异步操作的代码更加简洁和易读。通过 async 关键字定义一个异步函数,该函数返回一个 Promise 对象。可以在异步函数中使用 await 关键字等待异步操作完成,然后执行下一步操作。
模块化是指将程序划分为独立的模块,以便于代码的组织、复用和维护。在JavaScript中,有几种常见的模块化规范和标准,包括CommonJS、AMD、CMD、UMD和ES6 Module。它们之间有以下区别:
CommonJS:CommonJS是一种模块化规范,主要用于服务器端的JavaScript编程。它使用require()和module.exports语法来导入和导出模块。CommonJS模块是同步加载的,适用于在服务器环境下加载模块。
AMD:AMD(Asynchronous Module Definition)也是一种模块化规范,主要用于浏览器端的JavaScript编程。它通过定义模块和异步加载模块来解决浏览器中的模块加载问题。AMD规范中使用define()函数定义模块,使用require()函数异步加载模块。
CMD:CMD(Common Module Definition)是另一种浏览器端的模块化规范,与AMD相似,但更加懒加载。CMD规范中使用define()函数定义模块,使用require()函数异步加载模块。
UMD:UMD(Universal Module Definition)是一种兼容多种模块化规范的通用模块化解决方案。UMD模块可以在不同的环境中使用,包括浏览器和服务器端。
ES6 Module:ES6 Module是ES6(ECMAScript 2015)引入的官方模块化标准。ES6模块使用import和export语法来导入和导出模块。ES6模块可以静态解析依赖关系,使得编译器和工具可以进行更好的优化。
CommonJS主要用于服务器端,AMD和CMD主要用于浏览器端,UMD可以在不同的环境中使用,而ES6 Module是未来JavaScript开发的趋势,也可以通过工具转换成其他规范使用
事件循环(Event Loop)是JavaScript中执行异步代码的一种机制。JavaScript是单线程的,同一时间只能执行一个任务,如果执行的任务需要等待一段时间才能完成,那么就会导致程序阻塞,影响用户体验。为了解决这个问题,JavaScript引入了异步编程模型。
在JavaScript中,异步操作可以通过回调函数、Promise、Async/Await等方式来实现。而事件循环则是异步编程模型的基础,它的作用在于从任务队列中取出任务,并将其放到主线程上执行。
事件循环的执行过程如下:
宏任务包括整体代码块、setTimeout()、setInterval()、setImmediate(Node.js环境)、I/O操作、UI交互事件等
而微任务包括Promise.then()、MutationObserver等。
当事件循环执行完一个宏任务时,会先执行所有的微任务,然后再取出下一个宏任务执行。这个过程中,如果又产生了新的微任务,那么会立即执行这些微任务,然后再执行下一个宏任务。
CSS中设置display属性:display: none
CSS中visibility属性: visibility: hidden;该方法会使元素不可见,但仍占据页面空间。
CSS中opacity属性:opacity: 0;该方法会使元素完全透明,但仍然占据页面空间。
使用position属性:top: -9999px;该方法将元素移出屏幕范围,实现隐藏效果。
使用JavaScript动态修改样式:document.getElementById("elementId").style.display = "none"
; 可以通过JavaScript来动态修改元素的样式,将display属性设置为"none"来隐藏元素。
BFC(块级格式化上下文)它是页面中一个独立的渲染区域,拥有自己的布局规则。BFC具有一些特性,例如浮动元素不会覆盖BFC区域,BFC可以包含浮动元素,BFC可以阻止垂直外边距重叠等。
有几种方式可以触发一个元素的BFC,实现方法如下:
设置浮动(float):将元素设置为浮动,例如将其float属性设置为left或right,这会触发元素的BFC。
设置定位(position):将元素的position属性设置为absolute或fixed
设置overflow属性:将元素的overflow属性设置为非visible的值,如auto、hidden、scroll
设置display属性为inline-block、table-cell、table-caption或inline-flex
使用块级格式化上下文的根元素:HTML文档中的根元素(通常是html或body元素)本身就是一个BFC。
浏览器缓存机制是指浏览器在接收到服务器返回的资源后,根据设置的缓存策略来决定是否缓存该资源以及如何缓存的过程。
总的来说,强缓存是通过设置响应头信息来告诉浏览器在一定时间内直接使用本地缓存,而协商缓存则是通过与服务器进行验证来确定是否可以使用缓存。合理地配置强缓存和协商缓存可以提高网页加载速度,减少网络流量消耗。
同源定义: 如果两个url的协议,域名,端口号完全一致,那么这两个url就是同源的
同源策略是浏览器的一种安全策略,用于限制一个域下的文档或脚本如何与另一个域进行交互。
常见的跨域解决方法有以下几种:
JSONP: JSONP 是一种利用 script 标签可以跨域加载资源的特性来实现的跨域方式。通过在客户端动态创建 script 标签,并指定 src 属性为一个返回 JSON 数据的 URL,并将回调函数名称作为参数传递到服务器上,服务器会将 JSON 数据嵌入到回调函数中返回给客户端,从而实现跨域获取数据的目的。
CORS: CORS(Cross-Origin Resource Sharing)是一种跨域资源共享机制,可以使用 HTTP 头信息来告诉浏览器是否允许跨域请求。服务器在响应请求时,通过设置 Access-Control-Allow-Origin、Access-Control-Allow-Methods 等头信息来授权给定域的访问权限,从而允许跨域请求。
代理: 在客户端和服务端之间设置代理服务器,在代理服务器上完成跨域请求并将结果返回给客户端,客户端只需要和代理服务器进行交互。
useState() 是异步的
Vue 组件的生命周期是指组件从创建、挂载、更新、销毁等阶段经历的一系列过程,它们对应着不同的钩子函数,允许我们在不同的阶段执行自定义的逻辑。下面是常用的 Vue 组件生命周期钩子函数以及它们的执行顺序:
beforeCreate: 在实例初始化之后,在数据观测和事件配置之前被调用。
created: 在实例创建完成后被调用。此时实例已经完成了数据观测,属性和方法的运算,但尚未开始 DOM 编译和挂载。
beforeMount: 在挂载开始之前被调用。相关的 render 函数首次被调用。
mounted: 实例挂载到 DOM 后调用,此时组件已经生成对应的 DOM 结构,可以对 DOM 进行操作。
beforeUpdate: 数据更新时调用,但还未开始重新渲染 DOM。
updated: 数据更新并重新渲染 DOM 之后调用。
beforeUnmount(Vue 3.x)/ beforeDestroy(Vue 2.x): 实例销毁之前调用。在这个阶段可以进行一些清理工作。
unmounted(Vue 3.x)/ destroyed(Vue 2.x): 实例销毁之后调用,此时组件相关的监听器和子组件都已经被移除。
组件间生命周期的顺序是根据组件的嵌套关系决定的。父组件的生命周期钩子函数会先于子组件的生命周期钩子函数执行,即先从父组件开始逐级往下执行,再依次执行子组件的生命周期钩子函数。
例如,如果有一个父组件 A 包含一个子组件 B,则它们的生命周期顺序如下:
父组件 A的beforeCreate -> 父组件 A的 created -> 父组件 A的 beforeMount -> 子组件 B 的 beforeCreate -> 子组件 B 的 created -> 子组件 B 的 beforeMount -> 子组件 B 的 mounted -> 父组件 A 的 mounted
v-if 和 v-show 都是用来根据条件控制元素显示和隐藏的指令
v-if 是“真正”的条件渲染指令。如果表达式返回 true,Vue 会根据这个条件渲染元素并且插入到 DOM 中,如果为 false,则不会渲染该元素,也不会占据任何 DOM 空间。 使用 v-if 时,当条件切换时,Vue 会销毁或重新创建条件块及其内部的事件监听器和子组件。因此,如果初始渲染时条件为假,那么该元素不会被渲染到 DOM 中。
v-show 也用于根据条件来展示或隐藏元素,但它是通过修改元素的 display CSS 属性来实现的。如果表达式的值为 true,则元素会显示出来,如果为 false,则会隐藏。 使用 v-show 时,无论条件是 true 还是 false,元素都会被渲染到 DOM 中,只是通过修改 display 属性来控制其显示和隐藏。
key 的作用是帮助 Vue 进行更高效的列表渲染,以及在重新渲染时准确地跟踪每个节点的身份。