一、函数的arguments是不是数组,如何转化为数组?
arguments本身并不能调用数组的方法,它是另外一种对象类型,只不过属性从0开始排,一次是0,1,2...最后还有callee和length属性。我们把这样的对象成为类数组。
常见的类数组还有:
- getElementsByClassName/getElementsByTagName
- querySelector
我们需要将他们转换成数组,从而能够使用数组的方法,有哪些方法可以转换呢?
1、Array.from
2、[...arguments]
3、Array.prototype.slice.call(arguments)
4、最原始的再创建一个数组,遍历类数组属性把每个属性值放在里面
二、数组方法有哪些?遍历方法有何区别?
1、遍历方法
- 只有for系列中才有continue、break
- for
常规的for循环,continue、break、return有效 - for...in
遍历的是数组的key,是string类型。数组的私有属性也会遍历,continue、break、return有效。也可以在对象中,字符串中使用。数组的私有属性也会遍历。let arr= ['a','b','c','d'] arr[4] = 'f' for(let key in arr){ console.log(key) // 0,1,2,3,4 }
- for...of //es6新增方法
es6新增的方法,弥补了forEach和for...in的弊端,有continue、break、return,不会遍历数组的私有属性。遍历的是iterable(数组、map、set)的value,不能在对象、字符串中使用let arr= ['a','b','c','d'] arr[4] = 'f' for(let val of arr){ console.log(val) // a,b,c,d }
- forEach
没有return - map
映射 - filter
过滤 - every
所有的都返回true,则返回true,否则返回false - some
某一项返回true,则返回true,否则返回false - reduce
聚合,reduce函数接收两个参数,callback和initiaValue
reduce((accumulator, currentValue, currentIndex(可选), array(可选))=>{},initalValue(可选))。重点accumulator为initialValue或者每一次回调的返回值//统计字符串每个字母出现的次数 let str = '23wer43ertert' //常规做法 let ret = {} Array.from(str).forEach(item=>{ ret[item]?ret[item]++:ret[item]=1 ) return ret // 使用reduce Array.from(str).reduce((accumulator, current) => { accumulator[current] ? accumulator[current]++ : accumulator[current] = 1 return accumulator}, {})
2、栈和队列操作方法
- push
尾部追加元素 - pop
尾部移除一个元素 - shift
头部移除一个元素 - unshift
头部添加元素
3、es6新增方法
- find
- findIndex
- includes
- for...of
- flat
- keys
es6提供三个新的方法遍历数组,keys、values、entries,他们都返回一个遍历器对象Array.Iterator,可以用for of循环进行遍历,唯一的区别是keys()是对键明的遍历,values()是对健值得遍历,entries是对键值对的遍历let arr = [1,2,3] for(let key of arr.keys()){ console.log(key) // 0,1,2 }
- values
let arr = [1,2,3] for(let val of arr.values()){ console.log(val) // 1,2,3 }
- entries
let arr = [1,2,3] for(let [index, ele] of arr.entries()){ console.log(index, ele) // 0,1 1,2 2,3 }
- set
set和map是es6新增的数据结构,可以把set和map传递给Array.from(),生成一个新的数组。set - map
4、其他常用方法
- slice
slice(start, end),end可选,包前不包后,如果只有一个参数,则截取该下标到最后,与substring类似let arr = [1,2,3] let res = arr.slice(1,2) console.log(res) //[2] let res2 = arr.slice(1) console.log(res2) // [2,3]
- splice
splice(index, num, val...),index索引,num删除的数量,val(可选),插入的值,原数组会改变。let arr = [1,2,3] arr.splice(0,1) console.log(arr) // [2,3] arr.splice(1,1,4,5,6) // [2,4,5,6] console.log(arr)
- sort
- concat
- join
- indexOf
- reverse
- isArray
- toString
5、判断数组是否包含某个值
- indexOf
- includes
- find
- findIndex
6、数组扁平化
在前端开发中,偶尔会出现数据结构重叠数组,我们需要将多层级数组转化为一级数组,使其内容合并并且展开。
let arr = [1,[2,3,[4],5,[6]],7] // => [1,2,3,4,5,6,7]
- 调用es6中的flat方法
let ret = arr.flat(Infinity) // 参数为扁平化深度
- repace+split
let str = JSON.stringify(arr) return str.replace(/\[|\]/g, '').split(',')
- replace+parse
let str = JSON.stringify(arr) str = `[${str.replace(/\[|\]/g,'')}]` return JSON.parse(str)
- 普通递归
let ret = [] let flat = function(arr){ for(let i=0;i<arr.length;i++){ if(Array.isArray(arr[i])){ flat(arr[i]) }else{ ret.push(arr[i]) } } return flat }
- 使用reduce函数迭代
function flat(arr){ return arr.reduce((accumulator, current)=>{ return Array.isArray(current)? accumulator.concat(flat(current)): accumulator.concat(current) }, []) }
- 展开运算符
while(arr.some(item=>Array.isArray(item))){ arr = [].concat(...arr) } return arr
三、js数组中的高阶函数
1、什么是高阶函数
一个函数,如果接收另一个函数作为参数,或者返回值为一个函数,这种函数我们称之为高阶函数。
2、数组中的高阶函数
- map
- reduce
- filter
- sort
四、实现数组的map方法
Array.prototype.myMap = function (cb, scope) { // 判断类型 if (!Array.isArray(this)) { throw new TypeError("不是数组。。。。") } if (typeof cb !== 'function') { throw new TypeError(`${cb} is not a function`) } let T = Object(this) // arr let len = T.length, ret = [] for (let i = 0; i < len; i++) { ret.push(cb.call(scope, T[i], i, T)) } return ret}
五、实现数组的reduce方法
Array.prototype.myReduce = function(fn, initiaValue){
let arr = this, k=0,accomulator
if(!Array.isArray(arr)){
throw new Error('is no Array')
}
if(arr.length === 0){
throw new Error('array is empty')
}
if(!initiaValue){
accommulator = arr[0]
k++
}
for(;k<arr.length;k++){
accomulator = fn(acommulator, arr[k], k, arr)
}
return accomulator
}
六、实现数组的push、pop方法
Array.prototype.myPush = function(v){ console.log(this) let T = Object(this) if(!Array.isArray(T)){ throw new TypeError('不是数组。。。') } let len = T.length T[len] = v return v}
Array.prototype.myPop = function () { let T = Object(this), ret if (!Array.isArray(T)) { throw new TypeError('不是数组。。。') } let len = T.length; if (len === 0) { throw new TypeError('empty array') } ret = T[len - 1] delete T[len-1] T.length = len-1 return ret}
七、实现数组的filter方法
Array.prototype.myFilter = function (cb, scope) { let T = Object(this) if (!Array.isArray(T)) { throw new TypeError('不是数组。。。') } if (typeof cb !== 'function') { throw new TypeError(`${cb} is not a function`) } let len = T.length, ret = [] if (len === 0) { throw new TypeError('array is empty...') } for (let i = 0; i < len; i++) { if(cb.call(scope, T[i], i, T )){ ret.push(T[i]) } } return ret}
八、实现数组的splice方法
九、实现数组的sort方法
十、谈谈你对this的理解
this是当前函数上下文环境,是动态确定的,在函数运行时才能确定this所指对象
- 显示绑定
call、apply、bind - 隐式绑定
1、全局上下文:全局上下文默认this指向window,严格模式下指向undefined
2、直调用函数:指向windowlet obj = { fn: function(){ console.log(this) } } let f = obj.fn f() //window
3、对象.方法调用:上面obj.fn(),this指向obj对象
4、dom事件绑定
onclick或addEventListener中this默认指向绑定事件的元素
5、new+构造函数
this指向构造函数实例对象
6、箭头函数
箭头函数没有this,里面的this指向当前最近的非箭头函数的this
let obj = {
fn: function(){
let do =()=>{
console.log(this)
}
do()
}
}
obj.fn() //obj
十一、模拟实现call、apply、bind
- call
Function.prototype.myCall = function(context, ...rest){ context.fn = this context.fn(rest) delete context.fn } let obj = { name: 'ky' } function fn(...args){ console.log(this.name, args[0]) } fn.myCall(obj, 1,2,3)
- apply
Function.prototype.myApply = function(context, ...rest){ context.fn = this context.fn(rest) delete context.fn } let obj = { name: 'wky' } function fn(){ console.log(this.name, arguments) } fn.myApply (obj, [1,2,3])
- bind
Function.prototype.myBind = function(context, ...rest){ if(typeof this !== 'function'){ throw new Error('no a function') } context.fn = this return function (){ context.fn(...rest) delete context.fn } } let obj = { name: 'wky' } function fn(){ console.log(this.name, arguments) } fn.myBind(obj, [1,2,3])()
十二、模拟实现new
function Person(name, age){
this.name = name
this.age = age
}
Person.prototype.height = 100
function myNew(cons, ...rest){
let obj = Object.create(cons.prototype) //把构造函数原型作为新对象的原型,实现原型链上的继承
obj.fn = cons
obj.fn(...rest)
delete obj.fn
return obj
}
let p = myNew(Person, 'wky', 18)
console.log(p)
十三、js中的浅拷贝有哪些
- 什么是浅拷贝
let arr = [1,2,3] let newArr = arr 直接赋值这不涉及任何的拷贝,他们引用的是同一块内存空间。浅拷贝只是拷贝第一层对象,如果对象嵌套 浅拷贝无能为力
- 浅拷贝的方式
1、slice
2、Object.assignlet arr = [1,2,3,{a: 4}] let newArr = arr.slice() newArr[0] = 4 newArr[3].a = 5 console.log(arr) // [1,2,3, {a:5}]
3、...let obj = {a: 1} let newArr = Object.assign(obj,{a:2})
4、concat数组let obj = {a:1,b:2,c:{d:3}} let obj2 = {...obj}
5、手动实现let arr = [1,2,3,[1,2]] let arr2 = arr.concat([])
function shallowCopy(target){ let ret if(typeof target === 'object' && target !== null){ ret = Array.isArray(target)?[]:{} for(let key in target){ ret[key] = target[key] } }else{ ret = target } return ret }
十四、手写一个深拷贝
- 简易版及问题JSON.parse(JSON.stringify())
估计这个api能覆盖大多数的应用场景,但是对于某些严格的场景来说,这个api是有巨大坑的,问题如下
1、无法解决循环引用问题,会出现系统栈溢出,因为无限递归
2、无法复制函数let obj = {a: 1} obj.target = obj
3、无法拷贝一些特殊对象,如Map,Set - 递归实现深拷贝,可以复制函数,解决循环引用,解决Map、Set特殊对象
使用map保存已经拷贝过得对象,如果已经拷贝过,直接返回
Map,Set数据结构特殊处理 const getType = target => Object.prototype.toString.call(target) function deepClone(target, map = new WeakMap()) { if (typeof target === 'object' && target !== null) { if (map.get(target)) return target map.set(target, true) let type = getType(target) let ret = new target.constructor() switch (type) { case '[object Map]': target.forEach((item, key) => { ret.set(key, deepClone(item, map)) }) break; case '[object Set]': target.forEach(item => { ret.add(deepClone(item, map)) }) break; default: for (let key in target) { ret[key] = deepClone(target[key], map) } } return ret } else { return target } }
十五、谈谈对原型链继承的理解,手写一个继承
- 构造函数、实例对象、原型、原型链
画图展示 - 原型链继承+构造函数继承=组合式继承
function Foo() { this.name = 'foo' } Foo.prototype.fn = function () { console.log('foo 方法'); } function Coo() { // 继承父类构造函数里的属性和方法 Foo.call(this) this.type = "coo" } Coo.prototype.cooFn = function () { console.log('coo fn'); } Coo.prototype = new Foo() //原型链上的继承,把父类实例对象挂到子类原型上
- es6继承
class Foo { constructor() { this.name = 'Foo' this.fn = function () { } } } class Coo extends Foo { constructor(props) { super(props) this.type = "Coo" } }
- instanceof的理解,手写一个instanceof
原理是在原型链上找,如果找到原型链上有该构造函数的原型,返回true,否则返回falsefunction myInstanceof(left, right) { while (true) { if (left === null) { return false } if (left.constructor === right) { return true } left = left.__proto__ } }
- 如何判断一个数据类型
1、基本数据类型用typeof
2、引用数据类型
constructor
Object.prototype.toString.call(target)
3、判断一个数组
arr.constructor === Array
Object.prototype.toString.call(arr) // [object Array]
Array.isArray(arr)
arr instanceof Array
十六、深入理解js异步
- 什么是异步
1、为了避免dom渲染冲突,js是单线程的,也就是同一时间只能做一件事。异步是js单线程的解决方案。让同步代码先执行,等一段时间再执行异步的内容
2、常见的异步操作
微任务:promise、process.nextTick、
宏任务:setTimeout、setInterval、setImediate、 - 异步和event-loop
1、事件轮询是是js异步的具体实现。同步代码放在主线程中,异步的进入event-table并注册函数,console.log('1'); setTimeout(function() { console.log('2'); process.nextTick(function() { console.log('3'); }) new Promise(function(resolve) { console.log('4'); resolve(); }).then(function() { console.log('5') }) }) process.nextTick(function() { console.log('6'); }) new Promise(function(resolve) { console.log('7'); resolve(); }).then(function() { console.log('8') }) setTimeout(function() { console.log('9'); process.nextTick(function() { console.log('10'); }) new Promise(function(resolve) { console.log('11'); resolve(); }).then(function() { console.log('12') }) }) // 1,7,6,8,2,4,3,5,9,11,10,12
2、当指定的事件完成时,event-table会将这个函数移入event-queue
3、主线程内的任务执行完毕,会去event-queue读取对应的函数,进入主线程执行
4、先执行微任务,再执行宏任务
4、上述过程不断重复,也就是常说的event-loop - jq的解决方案
- promise
1、了解 Promise 吗?
2、Promise 解决的痛点是什么?
3、Promise 解决的痛点还有其他方法可以解决吗?如果有,请列举。
4、Promise 如何使用?
5、Promise 常用的方法有哪些?它们的作用是什么?
6、Promise 在事件循环中的执行过程是怎样的?
7、Promise 的业界实现都有哪些?
8、能不能手写一个 Promise 的 polyfill。 - generator
- async-await
十七、实现一个防抖函数
let st
function deounce(fn, wait){
if(st){
clearTimeout(st)
}
st = setTimeout(()=>{
fn()
}, wait)
}
十八、实现一个节流函数
let st
function throttle(fn, wait){
if(!st){
st = setTimeout(()=>{
st = null
fn()
}, wait)
}
}
十九、实现一个冒泡排序
function bubble(arr){
let len = arr.length
for(let i=0;i<len;i++){
for(let j=0;j<len;j++){
if(arr[j]>arr[j+1]){
let tem
tem = arr[j]
arr[j] = arr[j+1]
arr[j+1] = tem
}
}
}
return arr
}
二十、实现一个快速排序
function quickSort(arr){
if(arr.length <= 1){
return arr
}
let left = [], middle = [arr[0]], right = [], base = arr[0]
for(let i=0;i<arr.length;i++){
if(arr[i]<base){
left.push(arr[i])
}else if(arr[i] === base){
middle.push(arr[i])
}else{
right.push(arr[i])
}
}
return quickSort(left).concat(middle, quickSort(right))
}
二十一、BFC
1、盒模型
盒模型包括标准盒模型和IE盒模型
内容:content、padding、border、margin
区别:计算width和height方式不同,标准盒模型:content,IE盒模型content+padding+border
设置:标准盒模型:box-sizing:content-box,IE盒模型:box-sizing:border-box
2、BFC
概念:块级格式化上下文,是一块渲染区域,满足下面声明的元素会生成BFC
- float不为none
- overflow不为visible
- display为inline-block
- position为absolute或fixed
应用:
- 解决边距重叠问题:两个兄弟元素,margin边距会重叠,取较大者。
解决方法:将两个元素放到不同BFC环境下就好了。如A在body环境下,margin-bottom:100,B在box环境下,margin-top:200,给box加上BFC<style> #A { width: 100px; height: 100px; background: red; margin-bottom: 100px; } #B { width: 100px; height: 100px; background: blue; margin-top: 200px; }</style><body> <div id="A">A</div> <div style="overflow: hidden"> <div id="B">B</div> </div></body>
- 清除浮动:外面的outer并没有被内容撑开,原因是浮动脱离了文档流,与普通元素所处的流层不一样,不会被撑开
解决方法:在outer加上bfc<style> *{ margin: 0; padding: 0; } .outer{ border: 2px solid black; margin:100px; overflow: hidden; } .inner{ width: 100px; height: 100px; float: left; } .red{ background: red; } .green{ background: green; } .blue{ background: blue; } </style> </head> <body> <div class="outer"> <div class="inner red"></div> <div class="inner green"></div> <div class="inner blue"></div> </div> </body>
二十二、三栏布局
- 浮动
- 绝对定位
- flex布局
二十三、flex布局
二十四、事件委托
本质:事件冒泡,节约很多内存开销
二十五、正则匹配手机号
/^1(3|4|5|7|8)\d{9}$/
二十六、webpack配置
- webpack定义
webpack是一个模块打包工具,用来管理项目中的模块依赖,并编译输出模块所需的静态文件。它可以很好地管理、打包开发中所用到的HTML、CSS、JS和静态文件等,让开发更高效 - webpack基本功能和工作原理
1、代码转换:TypeScript编译成js、scss编译成css等
2、文件优化:压缩js、css、HTML代码,压缩合并图片等
3、代码分割:提取多个页面的公共代码,提取首屏不需要执行部分的代码,让其异步加载
4、模块合并:某个模块引用其他的模块和文件,需要构建功能,把模块合并成一个文件
5、热更新:监听本地原代码变化,自动构建,更新内容 - webpack配置结构
1、mode: 'development' // production
2、entry:打包文件入口
3、output:指定打包后资源位置entry: { page1: './page1.js', page2: './page2.js' }
4、module: {rules: []}: 配置各种loaderoutput: { path: path.resolve(__dirname__, './dist'), filename: '[name].[hash].js' }
5、plugins: []module: { rules: [{ test: /\.(jpg|png|jpeg|svg)$/, use: { loader: 'url-loader', options: { name: '[name]_[hash].[ext]', outputPath: './imgs', limit: 2048 } } }] }
6、devServer: {port: ,hot: true, proxy: '/api':{target: ''}}plugins: [new HtmlWepackPlugin({template: './index.html'})]
7、optimization:代码分割等配置 - 使用过哪些loader
1、file-loader: 打包文件
2、url-loader: 打包文件,与file-loader不同在于
3、css-loader{ test: /\.(png|jpg|jpeg|svg)$/, use: { loader: 'url-loader', options: { limit: 1000, //小于1000的文件打包出base64格式写入js,减少http请求 outputPath: './imgs' } } }
4、style-loader: 打包代码加入style标签中
5、scss-loader
6、postcss-loader:加上厂商前缀
7、babel-loader:处理es6/7,jsx,typescript等转译 - 使用过哪些plugin
1、html-webpack-plugin:指定html模板
2、clean-webpack-plugin:打包时先清理源目录文件plugins: [ new HtmlWebpackPlugin({template: './index.html'}) ]
3、mini-css-extract-plugin:
4、webpack.HotModuleReplacePlugin:热更新,使得代码修改后不用刷新浏览器就自动更新 - 什么是tree-shaking?
tree-shaking,摇树,是指在打包过程中,把没用到的代码除去,提高代码利用率,提高性能 - 区分环境合并配置文件:webpack-merge
- 代码分割:
optimization: { splitChunks: { async: 'all' } }
- webpack构建过程
1、从entry里面配置的入口文件递归解析所有依赖的module
2、根据配置的loader去找相应的转换规则
3、以entry为单位,一个entry和其所有依赖的module打包成一个chunk
4、把chunk转换成文件输出
5、在整个过程中,webpack会在恰当时机执行plugin里面定义的逻辑 - 手写一个loader
loader就是一个函数,接收原代码source,然后处理原代码,最后return source即可。不能用箭头函数,因为this由webpack填充,可以访问一些方法和属性 - 手写一个plugin
1、定义一个js函数
2、在函数原型上挂一个apply方法
3、指定一个compile钩子
4、处理完执行回调函数
二十七、面试题
Array.prototype.myMap = function (fn) { let arr = this if (!Array.isArray(arr)) { return 'no Array' } let ret = [] this.forEach((item, index) => { ret.push(fn(item, index, arr)) }) return ret}Array.prototype.myFilter = function (fn) { let arr = this if (Object.prototype.toString.call(arr) !== '[object Array]') { return 'no array' } let ret = [] arr.forEach((item, index) => { if (fn(item, index, arr)) { ret.push(item) } }) return ret}Array.prototype.myReduce = function (fn, inivialValue) { let arr = this, k = 0, accomulator if (!Array.isArray(arr)) { throw new Error('is no Array') } if (arr.length === 0) { throw new Error('arr is empty') } if (typeof fn !== 'function') { throw new Error('is not function') } if (!inivialValue) { k++ inivialValue = arr[0] } accomulator = inivialValue for (; k < arr.length; k++) { accomulator = fn(accomulator, arr[k], k, arr) } return accomulator}Function.prototype.myCall = function (context, ...args) { if (typeof this !== 'function') { throw new Error('is no a function') } context.fn = this context.fn(...args) delete context.fn}Function.prototype.myApply = function (context, args) { if (typeof this !== 'function') { throw new Error('is no function') } context.fn = this context.fn(...args) delete context.fn}Function.prototype.myBind = function (context, ...args) { if (typeof this !== 'function') { throw new Error('is no function') } context.fn = this return function () { context.fn(...args) delete context.fn }}function myNew(cons, ...args) { let obj = Object.create(cons.prototype) obj.fn = cons obj.fn(...args) delete obj.fn return obj}function myInstanceof(left, cons) { while (true) { if (left === null) { return false } if (left.__proto__ === cons.prototype) { return true } left = left.__proto__ }}function boubleSort(arr) { let len = arr.length for (let i = 0; i < len; i++) { for (let j = 0; j < len; j++) { if (arr[j] > arr[j + 1]) { let tem; tem = arr[j] arr[j] = arr[j + 1] arr[j + 1] = tem } } } return arr}function quickSort(arr) { if (arr.length <= 1) return arr let left = [], right = [], middle = [arr[0]], base = arr[0] for (let i = 1; i < arr.length; i++) { if (arr[i] <= base) { left.push(arr[i]) } else if (arr[i] === base) { middle.push(arr[i]) } else { right.push(arr[i]) } } return quickSort(left).concat(middle, quickSort(right))}let arr = [45, 32, 2, 76, 34, 45, 87, 9, 2, 1, 32, 2]console.log(quickSort(arr))console.log((myInstanceof({}, Array)));function Person(name, age) { this.name = name this.age = age}function toCamal(str) { // find_index_sum => findIndexSum let arr = str.split('_') let res = arr.map((item, i) => { // for (let i = 0; i < arr.length; i++) { if (i !== 0) { return item[0].toUpperCase() + item.substring(1) } else { return item } // } }) return res.join('')}console.log(toCamal('find_index_sum'))let p = myNew(Person, 'ky', 18)// console.log(p);// let arr = [1, 2, 3]// let filterRes = arr.myFilter((item, index, arr) => {// console.log(item, index, arr);// return item > 1// })// let res = arr.myMap((item, i, arr) => {// return `${item}-${i}-${arr.toString()}`// })// let reduceRes = arr.myReduce((accomulator, current, index, arr) => {// console.log(accomulator, current, index);// return accomulator + current// }, 1)// console.log(reduceRes);// let obj = {// name: 'obj'// }// function fn() {// console.log(this.name, arguments);// }// fn.bind(obj, [1, 2, 3])()/** * 123456789=>123,456,789 9-6 =3, * @param {*} num 9-1-8=0 9-1-7=1 9-1-6=2 * 1234567 7 1,234,567 * 7-i=7 * 7-i=6 * 5 * 4 */function formateMoney(num) { num += '' let ret = '' for (let i = 0; i < num.length; i++) { if ((num.length - i) % 3 === 1) { ret += `${num[i]},` } else { ret += num[i] } } // ret = ret.split('').reverse().join('') return ret.endsWith(',') ? ret.substr(0, ret.length - 1) : ret}// console.log(formateMoney(12345678))/**给定一个升序整形数组[0, 1, 2, 4, 5, 7, 13, 15, 16,],找出其中连续出现的数字区间为如下:["0->2", "4->5", "7", "13", "15->16"] * * @param {*} arr */function summaryRanges(arr) { // 补全代码 let ret = [] for (let i = 0; i < arr.length; i++) { if (arr[i - 1] + 1 !== arr[i] && arr[i] + 1 === arr[i + 1]) { ret.push(`${arr[i]}->`) } else if (arr[i] === arr[i - 1] + 1 && arr[i] + 1 !== arr[i + 1]) { ret = ret.join(',').replace(/->$/, `->${arr[i]}`).split(',') } else if (arr[i] === arr[i - 1] + 1 && arr[i] + 1 === arr[i + 1]) { continue } else { ret.push(arr[i]) } } console.log(ret);}summaryRanges([0, 1, 2, 4, 5, 7, 13, 15, 16, 17, 18, 19, 20, 21, 4, 5, 6, 7, 8, 4, 2, 3, 1, 2, 3, 4, 5, 6, 7])