前端面试必问问题18道---一篇文章进大厂

Glory ·
更新时间:2024-09-20
· 777 次阅读

谈谈数组Array在项目中常见的场景 求和,求最大(小)值,排序,多个数组合并(concat),去重,分割,find,indexOf,filter,join, toString等等 数组的concat,join,slice,toString方法不会改变原数组 数组的splice,push,pop,unshift,shift,sort,reverse方法会改变原数组 类数组怎么转化为数组

什么是类数组,就是属性要为索引(数字)属性,同时又必须有一个length属性来控制对象边界的一个特殊对象,特点:
1.不能使用数组的方法,他们不能使用Array的方法
2.拥有length属性,可以使用下标来访问元素,这两点和数组相同。

var obj = { '0': 'a', '1': 'b', '2': 'c', 'length': 3 }

转化为数组的方法有3种:

第一种,采用es6中的新方法Array.from() var myArray = ["value1", "value2", "value3"]; var mySet = new Set(myArray); console.log(Array.from(mySet)); //第一种方法Array.from() console.log([...mySet]); //第二种方法Array.from()

第二种,创建新数组,通过forEach遍历把类数组的元素添加到新数组中

第三种,通过Array.prototype.slice.call(arrayLike, 0)或者[].slice.call(arrayLike, 0)将类数组对象传入即可

第四种,扩展运算符 ... [...arguments]

// 将参数转为数组 var fn = function() { console.log([...arguments]); } fn(1, 2, 3) // 展开Set值 [...set.values()] common.js和ES6模块化的区别

详情可见:
js的4种模块化的使用方法和区别
import、require、export、module.exports 混合使用详解

不同规范产生的来由啊,随着技术的发展,js这门语言模块化的需要也越来迫切,随之而来就是各种规范,什么CMD、AMD各种规范也应运而生,现在在服务端形成CommonJS规范,前端呢ES6模块化的概念也深入人心。
毕竟一个是用于后端,一个是用于前端,所以还是有很大的不同,比较一下common.js和ES6模块化的区别:

前者支持动态导入,也就是 require(${path}/xx.js),后者目前不支持。

前者是同步导入,因为用于服务端,文件都在本地,同步导入即使卡住主线程影响也不大。而后者是异步导入,因为用于浏览器,需要下载文件,如果也采用同步导入会对渲染有很大影响。

前者在导出时都是值拷贝,就算导出的值变了,导入的值也不会改变,所以如果想更新值,必须重新导入一次。但是后者采用实时绑定的方式,导入导出的值都指向同一个内存地址,所以导入值会跟随导出值变化

require 在 ES6(bable将import转化为require) 和 CommonJS 中都支持,像Vue中一般引入CSS样式的时候会用到require,是因为样式一般需要同步导入的原因所致

即使我们使用了 ES6 的模块系统,如果借助 Babel 的转换,ES6 的模块系统最终还是会转换成 CommonJS 的规范。

两个数据互换,比如a=6&b=5,怎么做能a=5&b=6 // 第一种方法 var a = 10; var b = 20; var tmp = a; // tmp = 10; a = b; // a = 20; b = tmp; // b = 10; // 第二种方法 var a = 10; var b = 20; a = a + b; b = a - b; a = a - b; TS中枚举的定义

枚举(Enum)类型用于取值被限定在一定范围内的场景,比如一周只能有七天,颜色限定为红绿蓝等,通过enum关键字定义

enum Days {Sun, Mon, Tue, Wed, Thu, Fri, Sat}; console.log(Days[0] === 'Sun'); //true console.log(Days['Sat'] === 6); //true Set和Map

JavaScript的默认对象表示方式{}可以视为其他语言中的Map或Dictionary的数据结构,即一组键值对。

但是JavaScript的对象有个小问题,就是键必须是字符串。但实际上Number或者其他数据类型作为键也是非常合理的。为了解决这个问题,最新的ES6规范引入了新的数据类型Map和set.

Set [类数组] 集合是由一组无序且唯一(即不能重复)的项组成的,可以想象成集合是一个既没有重复元素,也没有顺序概念的数组 var a = new Set([1, 2, 3, {"1": "2"}, ["3","4"]]) Map [类对象] 它类似于对象,也是键值对的集合,但是“键”的范围不限于字符串,各种类型的值(包括对象)都可以当作键,是一种更完善的 Hash 结构实现。如果你需要“键值对”的数据结构,Map 比 Object 更合适 var m = new Map([['name', 'zhangsan'],['sex', 'male']]); console.log(m); //Map {"name" => "zhangsan", "sex" => "male"} 防抖与节流的原理及实现

节流 (throttle) 先执行,过一段时间再执行第二次

function throttlePro(delay, action) { var tId; return function () { var context = this; var arg = arguments; if (tId) return; tId = setTimeout(function () { action.apply(context, arg); clearTimeout(tId); // setTimeout 返回一个整数,clearTimeout 之后,tId还是那个整数,setInterval同样如此 tId = null; }, delay); } }

去抖 (debounce) 直至事件结束后,在处理,比如resize/scroll

function debounce(delay, action) { var tId; return function () { var context = this; var arg = arguments; if (tId) clearTimeout(tId); tId = setTimeout(function () { action.apply(context, arg); }, delay); } } window.onscroll = debounce(1000, print);

实现原理:利用定时器setTimeout

展开(扩展)运算符 ...的使用场景 替换Apply的函数调用 // 使用扩展运算符 var array1 = [1, 2, 3]; Math.min(...array1); 数组合并 // 数组合并 var array1 = [1, 2, 3]; var array2 = [4, 5, 6]; var array3 = [...array1, ...array2, 7, 8]; //[1,2,3,4,5,6,7,8] array1.push(...array2 )// 当然也可以使用concat等数组合并方法,但是扩展运算符提供了一种新的方式 转换类数组为数组 // es5 var fn = function() { console.log(Array.prototype.slice.apply(arguments)); } fn(1,2,3) //扩展运算符 var fn = function() { console.log([...arguments]); } fn(1,2,3) 怎么拍平一个二维数组(数组的扁平化)

核心原因, 用Array.isArray判断每一项
js数组拍平(数组扁平化)的六种方式

怎么判断一个目标类型

typeof

对于数组、函数、对象来说,其关系错综复杂,使用 typeof 都会统一返回 “object” 字符串, null也会返回’object’ 对NaN返回是’number’

instanceof

和typeof一样, [] instanceof Array[] instanceof Object 都是返回true

Object.prototype.toString.call()

Object.prototype.toString.call("jerry"); //[object String] Object.prototype.toString.call(12); //[object Number] Object.prototype.toString.call(true)); //[object Boolean] function Person(){}; Object.prototype.toString.call(new Person); //[object Object] // 判断是否为函数 function isFunction(it) { return Object.prototype.toString.call(it) === '[object Function]'; } // 判断是否为数组: function isArray(o) { return Object.prototype.toString.call(o) === '[object Array]'; } babel常用到的工具包 * babel: ES6转义的核心包 * babel-cli: 用于在终端使用babel,用命令行转码 * babel-core: 如果某些代码需要调用Babel的API进行转码,就要使用`babel-core`模块 * babel-loader: 执行转义的核心包 * babel-plugin-react-transform: 代替react-hot-loader的插件 * babel-preset-es2015: 现在被babel-preset-env取代了,被babel未来不会过时的(future-proof)”解决方案 * babel-preset-react: 转义react的jsx语法, * babel-preset-stage-0: `stage-0`包含`stage-1`, `stage-2`以及`stage-3`的所有功能 Vue中一个页面能否有多个插槽,怎么区分

vue中的插槽(slot)
vue中的插槽,父组件向子组件传递填充的模板代码,子组件中提供给父组件使用的一个占位符,用标签表示,比如HTML、组件等,填充的内容会替换掉子组件的标签(替换占位符)。

如果需要多个占位符,则直接加name区分即可,即

// 子组件插槽
我是第一个插槽 我是第二个插槽
// 父组件插入模板 yanggb1 yanggb2
Vue中data为啥不直接返回一个对象,而要return出去

不使用return包裹的数据会在项目的全局可见,会造成变量污染,使用return包裹后数据中变量只在当前组件中生效,不会影响其他组件

函数作用域和函数执行顺序

作用域有全局变量局部变量(也称为函数作用域)两类, ES6新增块级作用域,通过新增命令letconst来体现

function func(args){ if (true) { //let声明i let i = 6; //在if内打印i值 console.log('inside: ' + i);//6 } //在if外,再次打印i值 console.log('outside: ' + i);//i is not defined };

关于执行顺序,主要是由函数的定义方式中函数声明式定义 function student(){}所引起的变量置顶提升

聊聊new Function这个陌生面孔及函数作用域和函数执行顺序

js的深拷贝和浅拷贝

浅拷贝的时候如果数据是基本数据类型,那么就如同直接赋值那种,会拷贝其本身,如果除了基本数据类型之外还有一层对象,那么对于浅拷贝而言就只能拷贝其引用,对象的改变会反应到拷贝对象上;但是深拷贝就会拷贝多层,即使是嵌套了对象,也会都拷贝出来。

简单的拷贝方法(即对象中的值没有函数,数组,对象等复杂类型)

// 第一种 循环赋值 function simpleClone(initalObj) { var obj = {}; for ( var i in initalObj) { obj[i] = initalObj[i]; } return obj; } // 第二种 Object.assign var obj2 = { a: 10, b: 20, c: 30 }; var cloneObj2 = Object.assign({}, obj2); cloneObj2.b = 100; console.log(obj2); // { a: 10, b: 20, c: 30 } <-- 沒被改到 console.log(cloneObj2); // { a: 10, b: 100, c: 30 } // 第三种 JSON.parse(JSON.stringify(obj1)) 一般处理json数据时才会使用 var obj1 = { body: { a: 10 } }; var obj2 = JSON.parse(JSON.stringify(obj1));

(终于弄清楚JS的深拷贝和浅拷贝了)[https://blog.csdn.net/QTFYING/article/details/90407551]

聊聊Promise

诞生的原因:在JavaScript的世界中,所有代码都是单线程执行的。由于这个“缺陷”,导致JavaScript的所有网络操作,浏览器事件,都必须是异步执行。异步执行可以用回调函数实现,最典型的就是ajax,最终被ES6纳入标准,生成Promise

判断下面函数的执行顺序

console.log(1) setTimeout(()=>{console.log(2)},1000) async function fn(){ console.log(3) setTimeout(()=>{console.log(4)},20) return Promise.reject() } async function run(){ console.log(5) await fn() console.log(6) } run() //需要执行150ms左右 for(let i=0;i{ console.log(7) new Promise(resolve=>{ console.log(8) resolve() }).then(()=>{console.log(9)}) },0) console.log(10)

你真的了解Promise吗

闭包

定义:

百度百科:
闭包就是能够读取其他函数内部变量的函数。例如在javascript中,只有函数内部的子函数才能读取局部变量,所以闭包可以理解成“定义在一个函数内部的函数“。

《JavaScript高级编程指南》:
闭包是指有权访问另外一个函数作用域中的变量的函数。

MDN:
闭包是由函数以及创建该函数的词法环境组合而成,这个环境包含了这个闭包创建时所能访问的所有局部变量

有什么作用呢:

模仿块级作用域(匿名函数) 储存变量,延长变量生命周期 封装私有变量 函数柯里化 // 正常正则验证字符串 reg.test(txt) // 函数封装后 function check(reg, txt) { return reg.test(txt) } check(/\d+/g, 'test') //false check(/[a-z]+/g, 'test') //true // Currying后 function curryingCheck(reg) { return function(txt) { return reg.test(txt) } } var hasNumber = curryingCheck(/\d+/g) var hasLetter = curryingCheck(/[a-z]+/g) hasNumber('test1') // true hasNumber('testtest') // false hasLetter('21212') // false

关于闭包的this指向

var name = "听风是风"; var obj = { name: "行星飞行", sayName: function () { var that = this; return function () { console.log(this.name); //this -> window -> 听风是风 console.log(that.name); }; } }; obj.sayName()(); // 行星飞行

一篇文章看懂JS闭包,都要2020年了,你怎么能还不懂闭包?


作者:琴亭夜雨



前端面试 面试 前端

需要 登录 后方可回复, 如果你还没有账号请 注册新账号