根据近几年在前端中厂、大厂面试的同学的经历,总结出我们在拿offer前经常被问到的问题,有关JavaScript的问题能拿的出手的不是很多也不会很复杂,还是得需要我们对这门语言精通。
面试官不会精确到问你一个小问题,主要还是让我们谈一谈,说一说,发挥我们的语言组织能力,谈到这个知识点的时候你要是不会回答,就按是什么?有什么作用特点?再进行扩展开来聊。让面试官跟着我们的引导来走。扩展开来还是得按我们会的扩展。怕说了什么重要的自己答不上来,尽量往自己会的方向走。
接下来我们就看一看JavaScript的考点,当问到这些再慢慢学着扩展到下个问题。
1.js数组上面有哪些方法?
1. 增:
- push:将一个或多个元素添加到数组的末尾,并返回新数组的长度
- unshift:将一个或多个元素添加到数组的开头,并返回新数组的长度。
- splice:可以删除现有元素、添加新元素或替换数组中的元素。它接受多个参数,分别表示起始位置、要删除的元素个数(如果是 0 则表示不删除任何元素)、要添加的元素(可选)。 添加元素的位置由起始确定。以数组的形式返回删除的元素。
js
let arr = [1,2,3,4,5]
let len = arr.splice(1,2,6)
console.log(len); //[2,3]
- concat:用于合并两个或多个数组,并返回一个新数组,而不改变现有数组。
2.删:
- pop:从数组中移除最后一个元素,并返回该元素的值。同时,原数组的长度减一。
- shift:从数组中移除第一个元素,并返回该元素的值。同时,原数组的长度减一,并且所有后续元素的索引减一。
- splice
- slice:返回一个新数组,其中包含从开始到结束(不包括结束)的选定元素。原始数组不会被修改。一个参数默认到数组结尾。
js
let fruits = ['apple', 'banana', 'cherry', 'orange', 'pear'];
let selectedFruits = fruits.slice(1, 4);
// selectedFruits 现在为 ['banana', 'cherry', 'orange'],fruits 不变
3.改:
- reverse:用于颠倒数组中元素的顺序,即将数组中的元素倒序排列。
- sort:用于对数组中的元素进行排序。
4.查:
- indexOf:查询数组中的元素是否存在,存在就返回数组中第一个匹配项的索引值,如果不存在则返回 -1。
- lastIndexOf:返回数组中最后一个匹配项的索引,如果不存在则返回 -1。
- filter:创建一个新数组,其中包含通过指定函数测试的所有元素。
js
let numbers = [1, 2, 3, 4, 5];
let evenNumbers = numbers.filter(num => num % 2 === 0);
// evenNumbers 现在为 [2, 4],包含了原数组中所有偶数
- find:返回数组中满足提供的测试函数的第一个元素的值。如果没有找到,则返回
undefined
。
js
let numbers = [1, 2, 3, 4, 5];
let firstEvenNumber = numbers.find(num => num % 2 === 0);
// firstEvenNumber 的值为 2,因为它是数组中第一个偶数
- includes:判断数组是否包含某个元素,如果包含则返回
true
,否则返回false
。
5.转换:
- join:用于将数组中所有元素连接成一个字符串。你可以使用指定的分隔符来连接数组元素,如果不提供分隔符,默认使用逗号作为分隔符。
6.迭代:
- forEach:对数组中的每个元素执行指定的函数一次。它没有返回值(或者说返回值为
undefined
)。
js
let numbers = [1, 2, 3, 4, 5];
numbers.forEach(num => {
console.log(num);
});
// 输出:
// 1
// 2
// 3
// 4
// 5
- map:创建一个新数组,其元素是对原始数组中每个元素调用提供的函数的结果。
js
let numbers = [1, 2, 3, 4, 5];
let doubledNumbers = numbers.map(num => num * 2);
// doubledNumbers 现在为 [2, 4, 6, 8, 10]
- reduce:对数组中的每个元素执行一个提供的 reducer 函数(从左到右),将其结果汇总成单个值。
js
let numbers = [1, 2, 3, 4, 5];
let sum = numbers.reduce((acc, cur) => acc + cur, 0);
// sum 的值为 15,即 1 + 2 + 3 + 4 + 5
//acc是累加器,设置初始值为0,函数再执行一遍acc的值是上次函数执行的结果。
- filter:创建一个新数组,其中包含通过指定函数条件的所有元素。
- set:是 ES6 中引入的一种新的数据结构,它类似于数组,但是成员的值都是唯一的。
7.Array.from()
用来创建新数组的静态方法。它可以将类数组对象或可迭代对象(比如 Set、Map、字符串等)转换成一个新的数组实例。
Array.from()
方法的基本语法如下:
javascriptCopy
Array.from(arrayLike,[mapFunction,[thisArg]])
arrayLike
:要转换为数组的类数组对象或可迭代对象。mapFunction
(可选):对每个元素进行处理的映射函数。thisArg
(可选):执行mapFunction
时使用的this
值。
下面是一些 Array.from()
方法的示例用法:
- 将类数组对象转换为数组:
javascriptCopy
let arr = { 0: 'a', 1: 'b', 2: 'c', length: 3 };
let new = Array.from(arr);
console.log(new); // 输出: ['a', 'b', 'c']
- 使用映射函数处理每个元素:
javascriptCopy
let set = new Set([1, 2, 3]);
let newArray = Array.from(set, x => x * 2);
console.log(newArray); // 输出: [2, 4, 6]
- 使用箭头函数改变
this
值:
javascriptCopy
let arrayLike = 'hello';
let newArray = Array.from(arrayLike, function(char) {
return this.toUpperCase();
}, 'world');
console.log(newArray); // 输出: ['W', 'O', 'R', 'L', 'D']
2.js字符串上有哪些方法?
1.增:
concat:用于连接两个或多个字符串,并返回一个新的字符串,而不会改变原始字符串。
js
const str1 = 'Hello';
const str2 = 'world';
const str3 = '!';
const result = str1.concat( ' ' ,str2, str3);//添加一个空格
2.删:
-
slice:接收两个参数 [包括起始,终止可选),返回删除的字符,不修改原字符串。
如果接受的参数为负数则从字符串末尾从1的索引开始或终止,否则是空。
js
let str = 'hello,world'
let s1 = str.slice(0,2) //he
let s1 = str.slice(0) //hello,world
let s1 = str.slice(-5,-1) //worl
let s1 = str.slice(-2,0) //空
-
substring:接收两个参数,[起始,可选的结束),只接收正数,返回删除的元素,不修改原字符串。
-
substr:被弃用,不推荐使用,和slice相像,但是第二个参数是要删除的个数。
5.改:
-
replace :接收两个参数,被替换和替换的元素,返回一个新的字符串,不修改原字符串。可以使用正则匹配。
-
trim :可以去除字符串两端的空格,换行符,制表符。内部的无法去除。
-
padStart:接收两个参数,第一个是整个字符串可以到达的长度,第二个是需要添加的字符串。字符串长度超过第一个参数就不会再添加。返回新字符串,不修改原字符串。
js
let str ='hello'
console.log(str.padStart(10,'h')); // hhhhhhello
console.log(str.padStart(10)); //补充空格
let str ='hello'
let s1 = str.padStart(10,'h')
console.log(s1,str);//hhhhhhello 和 hello
-
padEnd :和padStart相似,但是从末尾添加。
-
trimStart :移除字符串开头的空格,返回新字符串,不修改原字符串。
-
trimEnd:移除字符串末尾的空格,返回新字符串,不修改原字符串。
-
toupCase:将字符串大写,无法选择需要大写的字符,返回新字符串,不修改原字符串。
-
toLowerCase将字符串小写,无法选择需要小写的字符,返回新字符串,不修改原字符串。
6.查:
-
indexOf:两个参数,需要匹配的字符,起始位置(可选),找到返回第一个匹配项索引,没有返回-1.
-
lastIndexOf:两个参数,需要匹配的字符,起始位置(可选),找到返回最后一个匹配项索引,没有返回-1.
-
includes:两个参数,需要匹配的字符,起始位置(可选),找到为true,没有返回false.
-
startsWith:是否以某些字符串开头。两个参数,需要匹配的字符,起始位置(可选),是为true,没有返回false.
-
endsWith:是否以某些字符串结尾。两个参数,需要匹配的字符,起始位置(可选),是为true,没有返回false.
7.转换:
- split:将字符串分割成子串并存入数组,不影响原字符串。
js
s="dog cat"
let arr = s.split(" ")
arr=['dog','cat']
3.谈谈js当中的类型转换机制
- 是什么: js中有原始类型和引用类型:
- 原始类型:number string boolean symbol null undefined Bigint
- 引用类型:Object Function Array Date RegExp Map Set WeakMap WeakSet
通常开发过程中,会用到一些显示的类型转换的手段来完成逻辑开发 .....
在v8执行的过程当中还存在另一种类型转换 --- 隐式类型转换
通常发生在 : 比较运算符 和算数运算符
-
比较运算符: == === != !== < <= > >= if while
-
算术运算符: + - * / %
[]==![] ???
这个涉及到隐式转换的问题,提到这个问题的,我们需要了解一个东西,那就是由复杂数据类型转换为基本数据类型。将引用类型转换为原始类型,在拆箱过程中会遵循ECMAScript规定的toPrimitive原则,对引用类型进行转换。在js中将复杂数据类型转换为基本数据类型会自动调用toPromitive函数。
!不会参与类型转换,但是!会希望[]变成布尔类型,所以右边的[]会变为true。左边的[]会调用原型上的toString方法转化为字符串。因为等号要使用数字判断,所以会触发Number方法转换为数字比较。
\] == !true \[\] == false '' == false 0 == 0 # 4. == 和 === 的区别? 1. == 是比较两个值,类型不一样会进行隐式转换,=== 是比较两个值的类型和值。 # 5.深浅拷贝的区别?如何实现一个深拷贝? [深浅拷贝](https://juejin.cn/post/7305321063668645888 "https://juejin.cn/post/7305321063668645888") 1. 深浅拷贝通常只针对于引用类型 * 浅拷贝: 只拷贝一层对象,复制这一层对象的原始值,如果有引用类型的话,就复制它的指针。 * 浅拷贝方法 1. object.assign() **let newObj = Object.assign({},obj)** 2. concat() **let newObj =\[\].concat(obj)** 3. 数组解构 ```js let arr = [1,2,3,{n:1}] let newObj = [...arr] arr[3].n = 2 console.log(newObj) ``` * 浅拷贝函数手写 ```js function shallowCopy(obj) { let newObj = {} for(let key in obj){ if(obj.hasOwnProperty(key)){ //显示原型上面的属性不拷贝 newObj[key] = obj[key] } } return newObj } ``` * 深拷贝: 层层拷贝,所有类型的属性值都会被复制,原对象的修改不会影响拷贝后的对象。 * 深拷贝方法 1. JSON.parse(JSON.stringify(obj)) ----无法处理undefined和symbol,function,循环引用 2. structuredClone(): 无法处理 symbol,funnction 3. [能处理循环引用](https://juejin.cn/post/7305321063668645888 "https://juejin.cn/post/7305321063668645888") * 手写深拷贝函数 ```js function deepCopy(obj){ let newObj = {} for(let key in obj){ if(obj.hasOwnProperty(key)){//用于检查对象自身是否具有指定的属性(而不是从原型链上继承而来的属性) if(typeof obj[key] !== 'object' || obj[key] === null){ newObj[key] = obj[key] }else{ newObj[key] = deepCopy(obj[key]) } } } return newObj } ``` # 6.说说你对闭包的理解? [闭包](https://juejin.cn/post/7300789760703283226 "https://juejin.cn/post/7300789760703283226") * 是什么: 当一个函数中的内部函数被拿到函数外部调用,又因为在js中内层作用域总是能访问外层作用域,那么内部函数存在对外部函数中变量的引用,这些变量的集合称之为闭包。 * 使用场景: 1. 创建私有变量 (全局变量不易维护) 2. 延长变量的生命周期 3. 实现颗粒化(柯里化) * 缺点: 会造成内存泄漏,内存被占据 # 7.什么是柯里化? * 是什么: 将一个接收多个参数的函数转变成多个只接受一个参数的函数 # 8.说说你对作用域的理解? [作用域](https://juejin.cn/post/7294474535022051355 "https://juejin.cn/post/7294474535022051355") * 是什么: 变量和函数能够生效的区域,这个区域叫作用域。 * 作用域的划分: 1. 全局作用域 2. 函数作用域 3. 块级作用域(const\|\|let 和{}) * 作用域链 作用域只能从内到外的访问,这种访问规则形成的链状关系我们称之为作用域链 * 词法作用域 指的是函数或者变量定义的区域 # 9.说说你对原型的理解 [原型](https://juejin.cn/post/7302254338856058899 "https://juejin.cn/post/7302254338856058899") * 是什么 显式原型:指的是函数身上自带的prototype属性,通常可以将一些方法和属性添加到到显示原型上,这样所有的实例对象都可以共享这些方法和属性。 隐式原型:__proto__是对象这种结构上的一个属性,其中包含了创建该对象时,隐式继承到的属性。 * 原型链: 创建一个实例对象时,实例对象的隐式原型 === 创建该对象的构造函数的显示原型,在js中对象的查找规则是先在对象中查找,找不到再去对象的隐式原型上查找,顺着隐式原型一层层往上找,直到找到null。 * 可用来实现属性的继承 # 10. 说说js中的继承 [继承](https://juejin.cn/post/7345757062108807220 "https://juejin.cn/post/7345757062108807220") * 是什么: 在js中,让一个子类可以访问父类的属性和方法 # 11. 说说js中的this [this的指向](https://juejin.cn/post/7301952490366500891 "https://juejin.cn/post/7301952490366500891") * 是什么: this是函数在运行过程中自动生成的一个对象,用来代指作用域的指向。 * 绑定规则 1. 默认绑定:当函数被独立调用时,函数的this指向window。(函数的词法作用域在哪里,this就指向哪个词法作用域,一层层往上找,直到找到最外层。) 2. 隐式绑定:当函数被一个对象所调用时,函数的this指向这个对象。 3. 隐式丢失:当函数调用前有多个对象,函数的this指向最近的对象。 4. 显式绑定:call,apply,bind。 5. new绑定:this指向实例对象。 * 箭头函数 箭头函数中的this是它外层非箭头函数的,指向也按照上述的绑定规则。 # 12.new的实现原理 1. 创建一个空对象 2. 将构造函数的原型绑定到对象的原型上 3. 将构造函数的this指向对象 4. 判断构造函数的返回值是否是引用类型,是就返回,不是就返回对象 * 构造函数有返回值,且为引用类型时会覆盖new当中的返回值 手写一个new实现 ```js Person.prototype.getName = function() { return this.name; } function Person(name) { this.name = name; return function () {} } function myNew(...args) { // [Person, 'Tom'] let obj = {} obj.__proto__ = args[0].prototype const res = args[0].apply(obj, args.slice(1)) return (typeof res === 'object' && res !== null) ? res : obj } let p = myNew(Person, 'Tom') ``` # 13. call,apply,bind 区别 [this的指向](https://juejin.cn/post/7301952490366500891 "https://juejin.cn/post/7301952490366500891") call:零散传参 call(obj,1,2) apply:数组传参 bind:零散传参,还会返回一个函数,需要调用这个函数,参数可以写在这个函数里面 # 14.说说js中的事件模型 [事件模型](https://juejin.cn/post/7319706549915107347 "https://juejin.cn/post/7319706549915107347") * 是什么 捕获,目标,冒泡三个阶段 * 分类 1. DOM0级:onclick(无法控制事件在捕获冒泡哪个阶段执行) 2. DOM1级:addeventListen(可以控制事件执行在哪个阶段) 3. IE模型:attachEvent(无法控制事件在捕获冒泡哪个阶段执行) # 15.说说typeof 和 instanceof的区别? [类型判断](https://juejin.cn/post/7304537142147416102 "https://juejin.cn/post/7304537142147416102") * typeof 能判断除了null之外的所有原始类型 * instanceof 主要适用于检查对象是否是由特定类或构造函数创建的实例。 * Object.prototype.toString.call(x) 1. \[\].toString() 数组版本的toString 2. Object.prototype.toString.call(\[\]) 3. \[\].toString() 对象版本的toString 该方法会让变量 x 调用对象上的toString函数,而toString返回值为\[object Object\]类型 * Array.isArray() 判断是否是数组 # 16. 说说Ajax的原理 [手写ajax](https://juejin.cn/post/7312059910485065791 "https://juejin.cn/post/7312059910485065791") * 是什么 Ajax * 实现过程 1. 创建一个XHR实例对象 2. 调用实例对象中的open方法与服务器建立连接 3. 调用实例对象的send方法发送请求 4. 监听onreadystatechange事件,通过判断readyState的值来获取到最终数据 5. 将数据更新到页面 # 17. 事件代理 * 事件委托 (多个子容器需要绑定相同的事件) 当我们操作大量数据时需要绑定事件,最简便的方式是循环为每一条数据绑定事件,这样会导致性能浪费。 所以需要用的事件代理。绑定父容器实现绑定一个事件代理到所有数据。 ```html
- 1
- 2
- 3
- 4
- 5