第79题:柯里化函数
实现一个Add函数,满足以下功能:
add(1); // 1
add(1)(2); // 3
add(1)(2)(3); // 6
add(1)(2, 3); // 6
add(1, 2)(3); // 6
add(1, 2, 3); // 6
- 解析:
function add(){ let args = [...arguments]; let addfun = function(){ args.push(...arguments); return addfun; } addfun.toString = function(){ return args.reduce((a,b)=>{ return a + b; }); } return addfun; }
参考:
第78题:给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序
const result = (arr) => arr.filter(Boolean).concat([...Array(arr.length - arr.filter(Boolean).length).fill(0)]) console.log(result([0,1,0,3,0,12,0,0]))
const arr = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 0, 0, 0, 1, 9, 9, 9, 0, 0, 0, 0, 1, 0, 3, 12, 0, 0, 0, 0]; const len = arr.length; console.log(len) for (let i = len; i >= 0; i--) { if (arr[i] === 0) { arr.splice(i, 1); arr.push(0) } } console.log(arr)
第77题:打印出 1 - 10000 之间的所有对称数, 例如:121、1331 等
[...Array(10000).keys()].filter((x) => { return x.toString().length > 1 && x === Number(x.toString().split('').reverse().join('')) })
let result=[] for(let i=1;i<10;i++){ result.push(i) result.push(i*11) for(let j=0;j<10;j++){ result.push(i*101+j*10) result.push(i*1001+j*110) } }
这个方法把1~9
考虑在内:
第76题: Promise.all 的使用、原理实现及错误处理
Promise.all(iterable)
方法返回一个 Promise 实例,此实例在 iterable 参数内所有的 promise 都“完成(resolved)”或参数中不包含 promise 时回调完成(resolve);如果参数中 promise 有一个失败(rejected),此实例回调失败(reject),失败原因的是第一个失败 promise 的结果。
- 如果传入的参数是一个
空的可迭代对象
,则返回一个已完成(already resolved)状态的 Promise。
- 如果传入的参数
不包含任何 promise
,则返回一个异步完成(asynchronously resolved) Promise
。注意:GoogleChrome 58 在这种情况下返回一个已完成(already resolved)状态的 Promise。
其它情况下返回一个处理中(pending)的Promise
。这个返回的 promise 之后会在所有的 promise 都完成或有一个 promise 失败时异步地变为完成或失败。 见下方关于“Promise.all 的异步或同步”示例。返回值将会按照参数内的 promise 顺序排列,而不是由调用 promise 的完成顺序决定。- 使用:
var p1 = Promise.resolve(3); var p2 = 1337; var p3 = new Promise((resolve, reject) => { setTimeout(resolve, 100, 'foo'); }); Promise.all([p1, p2, p3]).then(values => { console.log(values); // [3, 1337, "foo"] });
- 错误处理:
var p1 = new Promise((resolve, reject) => { setTimeout(resolve, 1000, 'one'); }); var p2 = new Promise((resolve, reject) => { setTimeout(resolve, 2000, 'two'); }); var p3 = new Promise((resolve, reject) => { setTimeout(resolve, 3000, 'three'); }); var p4 = new Promise((resolve, reject) => { setTimeout(resolve, 4000, 'four'); }); var p5 = new Promise((resolve, reject) => { reject('reject'); }); Promise.all([p1, p2, p3, p4, p5]).then(values => { console.log(values); }, reason => { console.log(reason) }); //From console: //"reject" //You can also use .catch Promise.all([p1, p2, p3, p4, p5]).then(values => { console.log(values); }).catch(reason => { console.log(reason) }); //From console: //"reject"
Promise.all
在任意一个传入的 promise 失败时返回失败
。例如,如果你传入的 promise中,有四个 promise 在一定的时间之后调用成功函数,有一个立即调用失败函数,那么 Promise.all 将立即变为失败。
参考:
第75题:input 搜索 如何处理中文输入
触发compositionstart
时,文本框会填入 “虚拟文本”(待确认文本),同时触发input事件;在触发compositionend
时,就是填入实际内容后(已确认文本)。例如:中文输入法输入内容时还没将中文插入到输入框就验证的问题,
为此,我们可以在中文输入完成以后才验证。即:在compositionend
发生后再进行逻辑的处理:
var cpLock = true; $('.com_search_input').on('compositionstart', function () { cpLock = false; // console.log("compositionstart") }); $('.com_search_input').on('compositionend', function () { cpLock = true; // console.log("compositionend") }); $(".com_search_input").on("input",function(e){ e.preventDefault(); var _this = this; // console.log("input"); setTimeout(function(){ if (cpLock) { //开始写逻辑 console.log("逻辑") } },0) })
使用延时器的原因:
因为选词结束的时候input会比compositionend先一步触发,此时cpLock还未调整为true,所以不能触发到console.log(“逻辑”),故用setTimeout将其优先级滞后。
第74题:Vue 的父组件和子组件生命周期钩子执行顺序是什么
- 加载渲染过程:
父beforeCreate->父created->父beforeMount->子beforeCreate->子created->子beforeMount->子mounted->父mounted
- 子组件更新过程:
父beforeUpdate->子beforeUpdate->子updated->父updated
- 父组件更新过程:
父beforeUpdate->父updated
- 销毁过程:
父beforeDestroy->子beforeDestroy->子destroyed->父destroyed
第73题:旋转数组
问题描述:
将包含* n* 个元素的数组向右旋转 *k *步。
例如,如果 n = 7 , k = 3,给定数组 [1,2,3,4,5,6,7] ,向右旋转后的结果为 [5,6,7,1,2,3,4]。
解析:利用解构数组和数组的splice方法
function rotateArr(arr,k) { return [...arr.splice(k+1),...arr]; } rotateArr([1,2,3,4,5,6,7],3);
第72题:对象的键名的转换
- 对象的键名
只能是字符串和 Symbol 类型
。 其他类型的键名会被转换成字符串类型
。对象转字符串默认会调用 toString 方法
。
考察:下列代码输出结果:
// example 1 var a={}, b='123', c=123; a[b]='b'; a[c]='c'; // c 的键名会被转换成字符串'123',这里会把 b 覆盖掉。 console.log(a[b]); // 'c' // example 2 var a={}, b=Symbol('123'), c=Symbol('123'); a[b]='b';// b 是 Symbol 类型,不需要转换 a[c]='c';// c 是 Symbol 类型,不需要转换。任何一个 Symbol 类型的值都是不相等的,所以不会覆盖掉 b console.log(a[b]);//'b' // example 3 var a={}, b={key:'123'}, c={key:'456'}; // b 不是字符串也不是 Symbol 类型,需要转换成字符串。 // 对象类型会调用 toString 方法转换成字符串 [object Object]。 a[b]='b'; // c 不是字符串也不是 Symbol 类型,需要转换成字符串。 // 对象类型会调用 toString 方法转换成字符串 [object Object]。这里会把 b 覆盖掉。 a[c]='c'; console.log(a[b]);//'c'
第71题:数组里面有10万个数据,取第一个元素和第10万个元素的时间相差多少
在jsperf中进行了测试,点击查看。
第70题:用Proxys实现双向数据绑定
参考
第69题:BFC、IFC、GFC和FFC
FC的全称是:Formatting Contexts,是W3C CSS2.1规范中的一个概念。它是页面中的一块渲染区域,并且有一套渲染规则,它决定了其子元素将如何定位,以及和其他元素的关系和相互作用。
BFC
块级格式化上下文:就是页面上的一个隔离的渲染区域,容器里面的子元素不会在布局上影响到外面的元素,反之也是如此。怎样会产生BFC:
- float的值不为none。
- overflow的值不为visible。
- position的值不为relative和static。
- display的值为table-cell, table-caption, inline-block中的任何一个。
IFC
内联格式化上下文:高度由其包含行内元素中最高的实际高度计算而来(不受到竖直方向的padding/margin影响)
。
IFC中的line box一般左右都贴紧整个IFC,但是会因为float元素而扰乱。float元素会位于IFC与与line box之间,使得line box宽度缩短。
同个ifc下的多个line box高度会不同。 IFC中不可能有块级元素的,当插入块级元素时(如p中插入div)会产生两个匿名块与div分隔开,即产生两个IFC,每个IFC对外表现为块级元素,与div垂直排列。
- IFC一般有什么用:
水平居中:当一个块要在环境中水平居中时,设置其为inline-block则会在外层产生IFC,通过text-align则可以使其水平居中。
垂直居中:创建一个IFC,用其中一个元素撑开父元素的高度,然后设置其vertical-align:middle,其他行内元素则可以在此父元素下垂直居中。
GFC
网格布局格式化上下文:当为一个元素设置display值为grid的时候
,此元素将会获得一个独立的渲染区域,我们可以通过在网格容器(grid container)上定义网格定义行(grid definition rows)和网格定义列(grid definition columns)属性各在网格项目(grid item)上定义网格行(grid row)和网格列(grid columns)为每一个网格项目(grid item)定义位置和空间。
GridLayout会有更加丰富的属性来控制行列,控制对齐以及更为精细的渲染语义和控制。
FFC
自适应格式化上下文:display值为flex或者inline-flex
的元素将会生成自适应容器(flex container),可惜这个牛逼的属性只有谷歌和火狐支持,不过在移动端也足够了,至少safari和chrome还是OK的,毕竟这俩在移动端
才是王道。
Flex Box 由伸缩容器和伸缩项目组成。通过设置元素的 display 属性为 flex 或 inline-flex 可以得到一个伸缩容器。设置为 flex 的容器被渲染为一个块级元素,而设置为 inline-flex 的容器则渲染为一个行内元素。
伸缩容器中的每一个子元素都是一个伸缩项目。伸缩项目可以是任意数量的。伸缩容器外和伸缩项目内的一切元素都不受影响。简单地说,Flexbox 定义了伸缩容器内伸缩项目该如何布局。
第68题:for与forEach性能
- for 循环没有任何额外的函数调用栈和上下文;
- forEach函数签名实际上是
array.forEach(function(currentValue, index, arr), thisValue)
它不是普通的 for 循环的语法糖,还有诸多参数和上下文需要在执行的时候考虑进来,这里可能拖慢性能;
参考
第67题:在字符串中查找匹配的字符串
const findStr = (s,t) => { let posArr = []; let index = s.search(t); while(index !== -1) { posArr.push(index); index = s.indexOf(t,index+t.length); } console.log(posArr); } findStr('sdfsdf123er123','123');//[6,11]
第66题:webpack热更新原理
参考
第65题:字符串大小写操作
如何把一个字符串的大小写取反(大写变小写小写变大写),例如 ’AbC’ 变成 ‘aBc’
function trans2Case(str) { let arr = str.split(''); arr = arr.map((item)=>{ return item === item.toUpperCase() ? item.toLowerCase() : item.toUpperCase(); }); return arr.join(''); } console.log(trans2Case('AbC'))
第64题: 随机生成一个长度为 10 的整数类型的数组,例如 [2, 10, 3, 4, 5, 11, 10, 11, 20],将其排列成一个新数组,要求新数组形式如下,例如 [[2, 3, 4, 5], [10, 11], [20]]。
function Array2Group(len) { //生成随机整数型数组 let arr = Array.from({length:len},(f)=>{return Math.floor(Math.random()*100)}) arr = arr.sort((a,b)=>{//升序排序 return a-b; }) //去重 arr = arr.filter((item,index)=>{ return item !== arr[index+1]; }) //分组,将连续的放在一组 let continueArr = [], tempArr = []; arr.map((item,index)=>{ tempArr.push(item); if(arr[index+1] !== ++item) { continueArr.push(tempArr); tempArr = []; } }) console.log(continueArr) } Array2Group(9);
第63题:Babe是将ES6转换为ES5的原理
Babel的功能非常纯粹,以字符串的形式将源代码传给它,它就会返回一段新的代码字符串(以及sourcemap)。他既不会运行你的代码,也不会将多个代码打包到一起,它就是个编译器,输入语言是ES6+,编译目标语言是ES5。
Babel的编译过程跟绝大多数其他语言的编译器大致同理,分为三个阶段:
- 解析:将代码字符串解析成抽象语法树
- 变换:对抽象语法树进行变换操作
- 再建:根据变换后的抽象语法树再生成代码字符串
第1步转换的过程中可以验证语法的正确性,同时由字符串变为对象结构后更有利于精准地分析以及进行代码结构调整。
第2步原理就很简单了,就是遍历这个对象所描述的抽象语法树,遇到哪里需要做一下改变,就直接在对象上进行操作,比如我把IfStatement给改成WhileStatement就达到了把条件判断改成循环的效果。在.babelrc里配置的presets和plugins都是在第2步工作的。
第3步也简单,递归遍历这颗语法树,然后生成相应的代码
- 抽象语法树是如何产生的:
- 分词:将整个代码字符串分割成 语法单元 数组
- 语义分析:在分词结果的基础之上分析 语法单元之间的关系
参考
- https://zhuanlan.zhihu.com/p/27289600
- http://www.ruanyifeng.com/blog/2016/01/babel.html
- https://moyueating.github.io/2017/07/08/%E6%B5%85%E8%B0%88babel%E5%8E%9F%E7%90%86%E4%BB%A5%E5%8F%8A%E4%BD%BF%E7%94%A8/
第62题:a.b.c.d和a[‘b’][‘c’][‘d’],哪个性能更高
a[‘b’][‘c’]和a.b.c,转换成AST前者的的树是含计算的,后者只是string literal,天然前者会消耗更多的计算成本,时间也更长
参考
第 61 题: 模拟实现promise的finally方法
promise.finally
方法用于指定不管 Promise 对象最后状态如何,都会执行的操作。即finally方法里面的操作,应该是与状态无关的,不依赖于 Promise 的执行结果
。
promise .then(result => {···}) .catch(error => {···}) .finally(() => {···});
上面代码中,不管promise最后的状态,在执行完then或catch指定的回调函数以后,都会执行finally方法指定的回调函数。
实现:
Promise.prototype.finally = function (callback) { let P = this.constructor; return this.then( value => P.resolve(callback()).then(() => value), reason => P.resolve(callback()).then(() => { throw reason }) ); };
参考
第 60 题: token加密的实现原理
1、后端生成一个secret(随机数)
2、后端利用secret和加密算法(如:HMAC-SHA256)对payload(如账号密码)生成一个字符串(token),返回前端
3、前端每次request在header中带上token
4、后端用同样的算法解密
参考
- 日常开发中的salt和token是什么?
- https://ninghao.net/blog/2834
- https://yhv5.com/token_1532.html?https://blog.csdn.net/qq_32784541/article/details/79655146
第 59 题: !important
不改变下面代码的情况下,设置width为330px
<img src="1.jpg" style="width:480px!important;”>
img {max-width:330px;}
第 58 题:求两个数组的交集
- filter、include方法
ar nums1 = [1, 2, 2, 1], nums2 = [2, 2, 3, 4]; // 1. // 有个问题, [NaN].indexOf(NaN) === -1 var newArr1 = nums1.filter(function(item) { return nums2.indexOf(item) > -1; }); console.log(newArr1); // 2. var newArr2 = nums1.filter((item) => { return nums2.includes(item); }); console.log(newArr2);
第 57 题:箭头函数与普通函数(function)的区别是什么?构造函数(function)可以使用 new 生成实例,那么箭头函数可以吗?为什么?
箭头函数是普通函数的简写,和普通函数相比,有以下几点差异:
- 函数体内的 this 对象,就是定义时所在的对象,而不是使用时所在的对象。
- 不可以使用 arguments 对象,该对象在函数体内不存在。如果要用,可以用 rest 参数代替。
- 不可以使用 yield 命令,因此箭头函数不能用作 Generator 函数。
- 不可以使用 new 命令,因为没有自己的 this,无法调用 call,apply。
同时,没有 prototype 属性 ,而 new 命令在执行时需要将构造函数的 prototype 赋值给新的对象的__proto__
,new 过程大致如下:
function newFunc(father, ...rest) { var result = {}; result.__proto__ = father.prototype; var result2 = father.apply(result, rest); if ( (typeof result2 === 'object' || typeof result2 === 'function') && result2 !== null ) { return result2; } return result; }
new运算符
js中,。new运算符创建了一个继承于其运算数的原型的新对象,然后调用该运算数,把新创建的对象绑定给this。
如果你忘记使用new运算符,你得到的是一个普通的函数调用,并且this被绑定到全局对象,而不是新创建的对象。
这意味着当你的函数尝试去初始化新成员属性时它将会污染全局变量。
如果你要使用new运算符,与new结合使用的函数应该以首字母大写的形式命名
,并且首字母大写的形式应该只用来命名那些构造函数。
一个更好的做法是,不去使用new。
第 56 题:分析比较 opacity: 0、visibility: hidden、display: none 优劣和适用场景
- 结构:
display:none: 会让元素完全从渲染树中消失,渲染的时候不占据任何空间, 不能点击,
visibility: hidden:不会让元素从渲染树消失,渲染元素继续占据空间,只是内容不可见,可以点击
opacity: 0: 不会让元素从渲染树消失,渲染元素继续占据空间,只是内容不可见,可以点击 - 继承:
display: none和opacity: 0:是非继承属性,子孙节点消失由于元素从渲染树消失造成,通过修改子孙节点属性无法显示。
visibility: hidden:是继承属性,子孙节点消失由于继承了hidden,通过设置visibility: visible;可以让子孙节点显式。 - 性能:
displaynone : 修改元素会造成文档回流,读屏器不会读取display: none元素内容,性能消耗较大
visibility:hidden: 修改元素只会造成本元素的重绘,性能消耗较少读屏器读取visibility: hidden元素内容
opacity: 0 : 修改元素会造成重绘,性能消耗较少
第55题:class设计,设计LazyMan类,实现下述功能
LazyMan('Tony'); // Hi I am Tony LazyMan('Tony').sleep(10).eat('lunch'); // Hi I am Tony // 等待了10秒... // I am eating lunch LazyMan('Tony').eat('lunch').sleep(10).eat('dinner'); // Hi I am Tony // I am eating lunch // 等待了10秒... // I am eating diner LazyMan('Tony').eat('lunch').eat('dinner').sleepFirst(5).sleep(10).eat('junk food'); // Hi I am Tony // 等待了5秒... // I am eating lunch // I am eating dinner // 等待了10秒... // I am eating junk food
- 解析
class LazyManClass { constructor(name) { this.taskList = []; this.name = name; console.log('Hi I am',this.name); let that = this; setTimeout(()=>{ that.next(); },0); } eat(name) { let that = this; let fn = (function(name) { return function() { console.log('I am eating',name); that.next(); } })(name); that.taskList.push(fn); console.log(that.taskList) return that; } sleepFirst(time) { let that = this; let fn = (function(time) { return function() { setTimeout(()=>{ console.log(`等待了${time}秒`); that.next(); },time*1000); } })(time); that.taskList.unshift(fn); console.log(that.taskList) return that; } sleep(time) { let that = this; let fn = (function(time){ return function() { setTimeout(()=>{ console.log(`等待了${time}秒`); that.next(); },time*1000); } })(time); that.taskList.push(fn); console.log(that.taskList) return that; } next() { let that = this; let fn = that.taskList.shift(); console.log(that.taskList) if(typeof fn ==='function') fn && fn() } } function LazyMan(name){ return new LazyManClass(name); } LazyMan('Tony').eat('lunch').eat('dinner').sleepFirst(5).sleep(10).eat('junk food');
第 54 题:某公司 1 到 12 月份的销售额存在一个对象里面,如下:{1:222, 2:123, 5:888},请把数据处理为如下结构:[222, 123, null, null, 888, null, null, null, null, null, null, null
Array.from()
:方法从一个类似数组或可迭代对象中创建一个新的数组实例,例如:
console.log(Array.from([1, 2, 3], x => x + x)); // expected output: Array [2, 4, 6]
- 解析问题
let obj = {1:222, 2:123, 5:888}; const result = Array.from({ length: 12 }) //先生成一个长度为12的数组,里面的值为undefined .map((_, index) => obj[index + 1] || null); //然后对数组赋值 console.log(result)
参考
第 53 题:冒泡排序如何实现,时间复杂度是多少, 如何改进?
冒泡排序这个算法的名字由来是因为越大的元素会经由交换慢慢“浮”到数列的顶端(升序或降序排列),就如同碳酸饮料中二氧化碳的气泡最终会上浮到顶端一样,故名“冒泡排序”。
冒泡排序算法的原理如下:
- 比较相邻的元素。如果第一个比第二个大,就交换他们两个。
- 对每一对相邻元素做同样的工作,从开始第一对到结尾的最后一对。在这一点,最后的元素应该会是最大的数。
- 针对所有的元素重复以上的步骤,除了最后一个。
- 持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。
冒泡排序最好的时间复杂度为 O(n),冒泡排序的最坏时间复杂度为 O(n^2) ,冒泡排序总的平均时间复杂度为 O(n^2) ,是一种稳定排序算法。
以升序为例:
// 升序冒泡 function bubbleSort(arr){ const array = [...arr] for(let i = 0, len = array.length; i < len - 1; i++){ for(let j = i + 1; j < len; j++) { if (array[i] > array[j]) { let temp = array[i] array[i] = array[j] array[j] = temp } } } return array }
上述这种算法不太好,因为就算你给一个已经排好序的数组,如[1,2,3,4,5,6] 它也会走一遍流程,白白浪费资源。
- 改进:加个标识,如果已经排好序了就直接跳出循环。
functionbubbleSort(arr){ const array = [...arr] let isOk = true for(let i = 0, len = array.length; i < len - 1; i++){ for(let j = i + 1; j < len; j++) { if (array[i] > array[j]) { let temp = array[i] array[i] = array[j] array[j] = temp isOk = false } } if(isOk){ break } } return array }
参考
- https://baike.baidu.com/item/%E5%86%92%E6%B3%A1%E6%8E%92%E5%BA%8F/4602306?fr=aladdin
- https://www.jianshu.com/p/5d44186b5263
第 52 题:执行顺序优先级
下列代码的执行结果:
var a = {n: 1}; var b = a; a.x = a = {n: 2}; console.log(a.x) //undefined console.log(b.x) // {n:2}
- 1、优先级。
.的优先级高于=
,所以先执行a.x,堆内存中的{n: 1}就会变成{n: 1, x: undefined},改变之后相应的b.x也变化了,因为指向的是同一个对象
。 - 2、
赋值操作是从右到左
,所以先执行a = {n: 2},a的引用就被改变了,然后这个返回值又赋值给了a.x,需要注意的是这时候a.x是第一步中的{n: 1, x: undefined}那个对象,其实就是b.x,相当于b.x = {n: 2}
第 51 题:让一个 div 水平垂直居中
// no.1 div.parent { display: flex; justify-content: center; align-items: center; } // no.2 div.parent { position: relative; } div.child { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); } //no.3 div.child { width: 50px; height: 10px; position: absolute; top: 50%; left: 50%; margin-left: -25px; margin-top: -5px; } //no.4 div.child { width: 50px; height: 10px; position: absolute; left: 0; top: 0; right: 0; bottom: 0; margin: auto; } //no.5 div.parent { display: grid; } div.child { justify-self: center; align-self: center; } //no.6 div.parent{ display:flex; } div.child{ margin:auto; }