数组去重
const arr = [1, 1, '1', 17, true, true, false, false, 'true', 'a', {}, {}];// => [1, '1', 17, true, false, 'true', 'a', {}, {}]复制代码
复制代码
方法一:利用 Set
const res1 = Array.from(new Set(arr));复制代码
复制代码
方法二:两层 for 循环+splice
const unique1 = arr => { let len = arr.length; for (let i = 0; i < len; i++) { for (let j = i + 1; j < len; j++) { if (arr[i] === arr[j]) { arr.splice(j, 1); // 每删除一个树,j--保证j的值经过自加后不变。同时,len--,减少循环次数提升性能 len--; j--; } } } return arr;}复制代码
复制代码
方法三:利用 indexOf
const unique2 = arr => { const res = []; for (let i = 0; i < arr.length; i++) { if (res.indexOf(arr[i]) === -1) res.push(arr[i]); } return res;}复制代码
复制代码
当然也可以用 include、filter,思路大同小异。
方法四:利用 include
const unique3 = arr => { const res = []; for (let i = 0; i < arr.length; i++) { if (!res.includes(arr[i])) res.push(arr[i]); } return res;}复制代码
复制代码
方法五:利用 filter
const unique4 = arr => { return arr.filter((item, index) => { return arr.indexOf(item) === index; });}复制代码
复制代码
方法六:利用 Map
const unique5 = arr => { const map = new Map(); const res = []; for (let i = 0; i < arr.length; i++) { if (!map.has(arr[i])) { map.set(arr[i], true) res.push(arr[i]); } } return res;}复制代码
复制代码
手写 Promise.race
该方法的参数是 Promise 实例数组, 然后其 then 注册的回调方法是数组中的某一个 Promise 的状态变为 fulfilled 的时候就执行. 因为 Promise 的状态只能改变一次, 那么我们只需要把 Promise.race 中产生的 Promise 对象的 resolve 方法, 注入到数组中的每一个 Promise 实例中的回调函数中即可.
Promise.race = function (args) { return new Promise((resolve, reject) => { for (let i = 0, len = args.length; i < len; i++) { args[i].then(resolve, reject) } })}复制代码
复制代码
手写 apply 函数
apply 函数的实现步骤:
判断调用对象是否为函数,即使我们是定义在函数的原型上的,但是可能出现使用 call 等方式调用的情况。
判断传入上下文对象是否存在,如果不存在,则设置为 window 。
将函数作为上下文对象的一个属性。
判断参数值是否传入
使用上下文对象来调用这个方法,并保存返回结果。
删除刚才新增的属性
返回结果
// apply 函数实现Function.prototype.myApply = function(context) { // 判断调用对象是否为函数 if (typeof this !== "function") { throw new TypeError("Error"); } let result = null; // 判断 context 是否存在,如果未传入则为 window context = context || window; // 将函数设为对象的方法 context.fn = this; // 调用方法 if (arguments[1]) { result = context.fn(...arguments[1]); } else { result = context.fn(); } // 将属性删除 delete context.fn; return result;};复制代码
复制代码
模拟 Object.create
Object.create()方法创建一个新对象,使用现有的对象来提供新创建的对象的__proto__。
// 模拟 Object.create
function create(proto) { function F() {} F.prototype = proto;
return new F();}复制代码
复制代码
实现 instanceOf
// 模拟 instanceoffunction instance_of(L, R) { //L 表示左表达式,R 表示右表达式 var O = R.prototype; // 取 R 的显示原型 L = L.__proto__; // 取 L 的隐式原型 while (true) { if (L === null) return false; if (O === L) // 这里重点:当 O 严格等于 L 时,返回 true return true; L = L.__proto__; }}复制代码
复制代码
查找字符串中出现最多的字符和个数
例: abbcccddddd -> 字符最多的是 d,出现了 5 次
let str = "abcabcabcbbccccc";let num = 0;let char = '';
// 使其按照一定的次序排列str = str.split('').sort().join('');// "aaabbbbbcccccccc"
// 定义正则表达式let re = /(\w)\1+/g;str.replace(re,($0,$1) => { if(num < $0.length){ num = $0.length; char = $1; }});console.log(`字符最多的是${char},出现了${num}次`);
复制代码
复制代码
实现数组的扁平化
(1)递归实现
普通的递归思路很容易理解,就是通过循环递归的方式,一项一项地去遍历,如果每一项还是一个数组,那么就继续往下遍历,利用递归程序的方法,来实现数组的每一项的连接:
let arr = [1, [2, [3, 4, 5]]];function flatten(arr) { let result = [];
for(let i = 0; i < arr.length; i++) { if(Array.isArray(arr[i])) { result = result.concat(flatten(arr[i])); } else { result.push(arr[i]); } } return result;}flatten(arr); // [1, 2, 3, 4,5]复制代码
复制代码
(2)reduce 函数迭代
从上面普通的递归函数中可以看出,其实就是对数组的每一项进行处理,那么其实也可以用 reduce 来实现数组的拼接,从而简化第一种方法的代码,改造后的代码如下所示:
let arr = [1, [2, [3, 4]]];function flatten(arr) { return arr.reduce(function(prev, next){ return prev.concat(Array.isArray(next) ? flatten(next) : next) }, [])}console.log(flatten(arr));// [1, 2, 3, 4,5]复制代码
复制代码
(3)扩展运算符实现
这个方法的实现,采用了扩展运算符和 some 的方法,两者共同使用,达到数组扁平化的目的:
let arr = [1, [2, [3, 4]]];function flatten(arr) { while (arr.some(item => Array.isArray(item))) { arr = [].concat(...arr); } return arr;}console.log(flatten(arr)); // [1, 2, 3, 4,5]复制代码
复制代码
(4)split 和 toString
可以通过 split 和 toString 两个方法来共同实现数组扁平化,由于数组会默认带一个 toString 的方法,所以可以把数组直接转换成逗号分隔的字符串,然后再用 split 方法把字符串重新转换为数组,如下面的代码所示:
let arr = [1, [2, [3, 4]]];function flatten(arr) { return arr.toString().split(',');}console.log(flatten(arr)); // [1, 2, 3, 4,5]复制代码
复制代码
通过这两个方法可以将多维数组直接转换成逗号连接的字符串,然后再重新分隔成数组。
(5)ES6 中的 flat
我们还可以直接调用 ES6 中的 flat 方法来实现数组扁平化。flat 方法的语法:arr.flat([depth])
其中 depth 是 flat 的参数,depth 是可以传递数组的展开深度(默认不填、数值是 1),即展开一层数组。如果层数不确定,参数可以传进 Infinity,代表不论多少层都要展开:
let arr = [1, [2, [3, 4]]];function flatten(arr) { return arr.flat(Infinity);}console.log(flatten(arr)); // [1, 2, 3, 4,5]复制代码
复制代码
可以看出,一个嵌套了两层的数组,通过将 flat 方法的参数设置为 Infinity,达到了我们预期的效果。其实同样也可以设置成 2,也能实现这样的效果。在编程过程中,如果数组的嵌套层数不确定,最好直接使用 Infinity,可以达到扁平化。 (6)正则和 JSON 方法 在第 4 种方法中已经使用 toString 方法,其中仍然采用了将 JSON.stringify 的方法先转换为字符串,然后通过正则表达式过滤掉字符串中的数组的方括号,最后再利用 JSON.parse 把它转换成数组:
let arr = [1, [2, [3, [4, 5]]], 6];function flatten(arr) { let str = JSON.stringify(arr); str = str.replace(/(\[|\])/g, ''); str = '[' + str + ']'; return JSON.parse(str); }console.log(flatten(arr)); // [1, 2, 3, 4,5]复制代码
复制代码
手写防抖函数
函数防抖是指在事件被触发 n 秒后再执行回调,如果在这 n 秒内事件又被触发,则重新计时。这可以使用在一些点击请求的事件上,避免因为用户的多次点击向后端发送多次请求。
// 函数防抖的实现function debounce(fn, wait) { let timer = null;
return function() { let context = this, args = arguments;
// 如果此时存在定时器的话,则取消之前的定时器重新记时 if (timer) { clearTimeout(timer); timer = null; }
// 设置定时器,使事件间隔指定事件后执行 timer = setTimeout(() => { fn.apply(context, args); }, wait); };}复制代码
复制代码
手写类型判断函数
function getType(value) { // 判断数据是 null 的情况 if (value === null) { return value + ""; } // 判断数据是引用类型的情况 if (typeof value === "object") { let valueClass = Object.prototype.toString.call(value), type = valueClass.split(" ")[1].split(""); type.pop(); return type.join("").toLowerCase(); } else { // 判断数据是基本数据类型的情况和函数的情况 return typeof value; }}复制代码
复制代码
实现 apply 方法
apply 原理与 call 很相似,不多赘述
// 模拟 applyFunction.prototype.myapply = function(context, arr) { var context = Object(context) || window; context.fn = this;
var result; if (!arr) { result = context.fn(); } else { var args = []; for (var i = 0, len = arr.length; i < len; i++) { args.push("arr[" + i + "]"); } result = eval("context.fn(" + args + ")"); }
delete context.fn; return result;};复制代码
复制代码
实现字符串的 repeat 方法
输入字符串 s,以及其重复的次数,输出重复的结果,例如输入 abc,2,输出 abcabc。
function repeat(s, n) { return (new Array(n + 1)).join(s);}复制代码
复制代码
递归:
function repeat(s, n) { return (n > 0) ? s.concat(repeat(s, --n)) : "";}复制代码
复制代码
字符串出现的不重复最长长度
用一个滑动窗口装没有重复的字符,枚举字符记录最大值即可。用 map 维护字符的索引,遇到相同的字符,把左边界移动过去即可。挪动的过程中记录最大长度:
var lengthOfLongestSubstring = function (s) { let map = new Map(); let i = -1 let res = 0 let n = s.length for (let j = 0; j < n; j++) { if (map.has(s[j])) { i = Math.max(i, map.get(s[j])) } res = Math.max(res, j - i) map.set(s[j], j) } return res};复制代码
复制代码
实现 add(1)(2)(3)
函数柯里化概念: 柯里化(Currying)是把接受多个参数的函数转变为接受一个单一参数的函数,并且返回接受余下的参数且返回结果的新函数的技术。
1)粗暴版
function add (a) {return function (b) { return function (c) { return a + b + c; }}}console.log(add(1)(2)(3)); // 6复制代码
复制代码
2)柯里化解决方案
var add = function (m) { var temp = function (n) { return add(m + n); } temp.toString = function () { return m; } return temp;};console.log(add(3)(4)(5)); // 12console.log(add(3)(6)(9)(25)); // 43复制代码
复制代码
对于 add(3)(4)(5),其执行过程如下:
先执行 add(3),此时 m=3,并且返回 temp 函数;
执行 temp(4),这个函数内执行 add(m+n),n 是此次传进来的数值 4,m 值还是上一步中的 3,所以 add(m+n)=add(3+4)=add(7),此时 m=7,并且返回 temp 函数
执行 temp(5),这个函数内执行 add(m+n),n 是此次传进来的数值 5,m 值还是上一步中的 7,所以 add(m+n)=add(7+5)=add(12),此时 m=12,并且返回 temp 函数
由于后面没有传入参数,等于返回的 temp 函数不被执行而是打印,了解 JS 的朋友都知道对象的 toString 是修改对象转换字符串的方法,因此代码中 temp 函数的 toString 函数 return m 值,而 m 值是最后一步执行函数时的值 m=12,所以返回值是 12。
function add (...args) { //求和 return args.reduce((a, b) => a + b)}function currying (fn) { let args = [] return function temp (...newArgs) { if (newArgs.length) { args = [ ...args, ...newArgs ] return temp } else { let val = fn.apply(this, args) args = [] //保证再次调用时清空 return val } }}let addCurry = currying(add)console.log(addCurry(1)(2)(3)(4, 5)()) //15console.log(addCurry(1)(2)(3, 4, 5)()) //15console.log(addCurry(1)(2, 3, 4, 5)()) //15复制代码
复制代码
手写 call 函数
call 函数的实现步骤:
判断调用对象是否为函数,即使我们是定义在函数的原型上的,但是可能出现使用 call 等方式调用的情况。
判断传入上下文对象是否存在,如果不存在,则设置为 window 。
处理传入的参数,截取第一个参数后的所有参数。
将函数作为上下文对象的一个属性。
使用上下文对象来调用这个方法,并保存返回结果。
删除刚才新增的属性。
返回结果。
// call函数实现Function.prototype.myCall = function(context) { // 判断调用对象 if (typeof this !== "function") { console.error("type error"); } // 获取参数 let args = [...arguments].slice(1), result = null; // 判断 context 是否传入,如果未传入则设置为 window context = context || window; // 将调用函数设为对象的方法 context.fn = this; // 调用函数 result = context.fn(...args); // 将属性删除 delete context.fn; return result;};复制代码
复制代码
实现数组的乱序输出
主要的实现思路就是:
var arr = [1,2,3,4,5,6,7,8,9,10];for (var i = 0; i < arr.length; i++) { const randomIndex = Math.round(Math.random() * (arr.length - 1 - i)) + i; [arr[i], arr[randomIndex]] = [arr[randomIndex], arr[i]];}console.log(arr)复制代码
复制代码
还有一方法就是倒序遍历:
var arr = [1,2,3,4,5,6,7,8,9,10];let length = arr.length, randomIndex, temp; while (length) { randomIndex = Math.floor(Math.random() * length--); temp = arr[length]; arr[length] = arr[randomIndex]; arr[randomIndex] = temp; }console.log(arr)复制代码
复制代码
类数组转化为数组
类数组是具有 length 属性,但不具有数组原型上的方法。常见的类数组有 arguments、DOM 操作方法返回的结果。
方法一:Array.from
Array.from(document.querySelectorAll('div'))复制代码
复制代码
方法二:Array.prototype.slice.call()
Array.prototype.slice.call(document.querySelectorAll('div'))复制代码
复制代码
方法三:扩展运算符
[...document.querySelectorAll('div')]复制代码
复制代码
方法四:利用 concat
Array.prototype.concat.apply([], document.querySelectorAll('div'));复制代码
复制代码
Array.prototype.filter()
Array.prototype.filter = function(callback, thisArg) { if (this == undefined) { throw new TypeError('this is null or not undefined'); } if (typeof callback !== 'function') { throw new TypeError(callback + 'is not a function'); } const res = []; // 让O成为回调函数的对象传递(强制转换对象) const O = Object(this); // >>>0 保证len为number,且为正整数 const len = O.length >>> 0; for (let i = 0; i < len; i++) { // 检查i是否在O的属性(会检查原型链) if (i in O) { // 回调函数调用传参 if (callback.call(thisArg, O[i], i, O)) { res.push(O[i]); } } } return res;}复制代码
复制代码
对于>>>0有疑问的:解释>>>0的作用
实现每隔一秒打印 1,2,3,4
// 使用闭包实现for (var i = 0; i < 5; i++) { (function(i) { setTimeout(function() { console.log(i); }, i * 1000); })(i);}// 使用 let 块级作用域for (let i = 0; i < 5; i++) { setTimeout(function() { console.log(i); }, i * 1000);}复制代码
复制代码
使用 Promise 封装 AJAX 请求
// promise 封装实现:function getJSON(url) { // 创建一个 promise 对象 let promise = new Promise(function(resolve, reject) { let xhr = new XMLHttpRequest(); // 新建一个 http 请求 xhr.open("GET", url, true); // 设置状态的监听函数 xhr.onreadystatechange = function() { if (this.readyState !== 4) return; // 当请求成功或失败时,改变 promise 的状态 if (this.status === 200) { resolve(this.response); } else { reject(new Error(this.statusText)); } }; // 设置错误监听函数 xhr.onerror = function() { reject(new Error(this.statusText)); }; // 设置响应的数据类型 xhr.responseType = "json"; // 设置请求头信息 xhr.setRequestHeader("Accept", "application/json"); // 发送 http 请求 xhr.send(null); }); return promise;}复制代码
复制代码
评论