32個(gè)手撕JS,徹底擺脫初級(jí)前端(面試高頻)-上篇
關(guān)于源碼都緊遵規(guī)范,都可跑通MDN示例,其余的大多會(huì)涉及一些關(guān)于JS的應(yīng)用題和本人面試過程
01.數(shù)組扁平化
數(shù)組扁平化是指將一個(gè)多維數(shù)組變?yōu)橐粋€(gè)一維數(shù)組
- const arr = [1, [2, [3, [4, 5]]], 6];
- // => [1, 2, 3, 4, 5, 6]
- 復(fù)制代碼
方法一:使用flat()
- const res1 = arr.flat(Infinity);
- 復(fù)制代碼
方法二:利用正則
- const res2 = JSON.stringify(arr).replace(/\[|\]/g, '').split(',');
- 復(fù)制代碼
但數(shù)據(jù)類型都會(huì)變?yōu)樽址?/p>
方法三:正則改良版本
- const res3 = JSON.parse('[' + JSON.stringify(arr).replace(/\[|\]/g, '') + ']');
- 復(fù)制代碼
方法四:使用reduce
- const flatten = arr => {
- return arr.reduce((pre, cur) => {
- return pre.concat(Array.isArray(cur) ? flatten(cur) : cur);
- }, [])
- }
- const res4 = flatten(arr);
- 復(fù)制代碼
方法五:函數(shù)遞歸
- const res5 = [];
- const fn = arr => {
- for (let i = 0; i < arr.length; i++) {
- if (Array.isArray(arr[i])) {
- fn(arr[i]);
- } else {
- res5.push(arr[i]);
- }
- }
- }
- fn(arr);
- 復(fù)制代碼
02.數(shù)組去重
- const arr = [1, 1, '1', 17, true, true, false, false, 'true', 'a', {}, {}];
- // => [1, '1', 17, true, false, 'true', 'a', {}, {}]
- 復(fù)制代碼
方法一:利用Set
- const res1 = Array.from(new Set(arr));
- 復(fù)制代碼
方法二:兩層for循環(huán)+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);
- // 每刪除一個(gè)樹,j--保證j的值經(jīng)過自加后不變。同時(shí),len--,減少循環(huán)次數(shù)提升性能
- len--;
- j--;
- }
- }
- }
- return arr;
- }
- 復(fù)制代碼
方法三:利用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;
- }
- 復(fù)制代碼
當(dāng)然也可以用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;
- }
- 復(fù)制代碼
方法五:利用filter
- const unique4 = arr => {
- return arr.filter((item, index) => {
- return arr.indexOf(item) === index;
- });
- }
- 復(fù)制代碼
方法六:利用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;
- }
- 復(fù)制代碼
03.類數(shù)組轉(zhuǎn)化為數(shù)組
類數(shù)組是具有l(wèi)ength屬性,但不具有數(shù)組原型上的方法。常見的類數(shù)組有arguments、DOM操作方法返回的結(jié)果。
方法一:Array.from
- Array.from(document.querySelectorAll('div'))
- 復(fù)制代碼
方法二:Array.prototype.slice.call()
- Array.prototype.slice.call(document.querySelectorAll('div'))
- 復(fù)制代碼
方法三:擴(kuò)展運(yùn)算符
- [...document.querySelectorAll('div')]
- 復(fù)制代碼
方法四:利用concat
- Array.prototype.concat.apply([], document.querySelectorAll('div'));
- 復(fù)制代碼
04.Array.prototype.filter()
- rray.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成為回調(diào)函數(shù)的對(duì)象傳遞(強(qiáng)制轉(zhuǎn)換對(duì)象)
- const O = Object(this);
- // >>>0 保證len為number,且為正整數(shù)
- const len = O.length >>> 0;
- for (let i = 0; i < len; i++) {
- // 檢查i是否在O的屬性(會(huì)檢查原型鏈)
- if (i in O) {
- // 回調(diào)函數(shù)調(diào)用傳參
- if (callback.call(thisArg, O[i], i, O)) {
- res.push(O[i]);
- }
- }
- }
- return res;
- }
- 復(fù)制代碼
對(duì)于>>>0有疑問的:解釋>>>0的作用
05.Array.prototype.map()
- Array.prototype.map = function(callback, thisArg) {
- if (this == undefined) {
- throw new TypeError('this is null or not defined');
- }
- if (typeof callback !== 'function') {
- throw new TypeError(callback + ' is not a function');
- }
- const res = [];
- // 同理
- const O = Object(this);
- const len = O.length >>> 0;
- for (let i = 0; i < len; i++) {
- if (i in O) {
- // 調(diào)用回調(diào)函數(shù)并傳入新數(shù)組
- res[i] = callback.call(thisArg, O[i], i, this);
- }
- }
- return res;
- }
- 復(fù)制代碼
06.Array.prototype.forEach()
forEach跟map類似,唯一不同的是forEach是沒有返回值的。
- Array.prototype.forEach = function(callback, thisArg) {
- if (this == null) {
- throw new TypeError('this is null or not defined');
- }
- if (typeof callback !== "function") {
- throw new TypeError(callback + ' is not a function');
- }
- const O = Object(this);
- const len = O.length >>> 0;
- let k = 0;
- while (k < len) {
- if (k in O) {
- callback.call(thisArg, O[k], k, O);
- }
- k++;
- }
- }
- 復(fù)制代碼
07.Array.prototype.reduce()
- Array.prototype.reduce = function(callback, initialValue) {
- if (this == undefined) {
- throw new TypeError('this is null or not defined');
- }
- if (typeof callback !== 'function') {
- throw new TypeError(callbackfn + ' is not a function');
- }
- const O = Object(this);
- const len = this.length >>> 0;
- let accumulator = initialValue;
- let k = 0;
- // 如果第二個(gè)參數(shù)為undefined的情況下
- // 則數(shù)組的第一個(gè)有效值作為累加器的初始值
- if (accumulator === undefined) {
- while (k < len && !(k in O)) {
- k++;
- }
- // 如果超出數(shù)組界限還沒有找到累加器的初始值,則TypeError
- if (k >= len) {
- throw new TypeError('Reduce of empty array with no initial value');
- }
- accumulator = O[k++];
- }
- while (k < len) {
- if (k in O) {
- accumulator = callback.call(undefined, accumulator, O[k], k, O);
- }
- k++;
- }
- return accumulator;
- }
- 復(fù)制代碼
08.Function.prototype.apply()
第一個(gè)參數(shù)是綁定的this,默認(rèn)為window,第二個(gè)參數(shù)是數(shù)組或類數(shù)組
- Function.prototype.apply = function(context = window, args) {
- if (typeof this !== 'function') {
- throw new TypeError('Type Error');
- }
- const fn = Symbol('fn');
- context[fn] = this;
- const res = context[fn](...args);
- delete context[fn];
- return res;
- }
- 復(fù)制代碼
09.Function.prototype.call
于call唯一不同的是,call()方法接受的是一個(gè)參數(shù)列表
- Function.prototype.call = function(context = window, ...args) {
- if (typeof this !== 'function') {
- throw new TypeError('Type Error');
- }
- const fn = Symbol('fn');
- context[fn] = this;
- const res = this[fn](...args);
- delete this.fn;
- return res;
- }
- 復(fù)制代碼
10.Function.prototype.bind
- Function.prototype.bind = function(context, ...args) {
- if (typeof this !== 'function') {
- throw new Error("Type Error");
- }
- // 保存this的值
- var self = this;
- return function F() {
- // 考慮new的情況
- if(this instanceof F) {
- return new self(...args, ...arguments)
- }
- return self.apply(context, [...args, ...arguments])
- }
- }
- 復(fù)制代碼
11.debounce(防抖)
觸發(fā)高頻時(shí)間后n秒內(nèi)函數(shù)只會(huì)執(zhí)行一次,如果n秒內(nèi)高頻時(shí)間再次觸發(fā),則重新計(jì)算時(shí)間。
- const debounce = (fn, time) => {
- let timeout = null;
- return function() {
- clearTimeout(timeout)
- timeout = setTimeout(() => {
- fn.apply(this, arguments);
- }, time);
- }
- };
- 復(fù)制代碼
防抖常應(yīng)用于用戶進(jìn)行搜索輸入節(jié)約請(qǐng)求資源,window觸發(fā)resize事件時(shí)進(jìn)行防抖只觸發(fā)一次。
12.throttle(節(jié)流)
高頻時(shí)間觸發(fā),但n秒內(nèi)只會(huì)執(zhí)行一次,所以節(jié)流會(huì)稀釋函數(shù)的執(zhí)行頻率。
- const throttle = (fn, time) => {
- let flag = true;
- return function() {
- if (!flag) return;
- flag = false;
- setTimeout(() => {
- fn.apply(this, arguments);
- flag = true;
- }, time);
- }
- }
- 復(fù)制代碼
節(jié)流常應(yīng)用于鼠標(biāo)不斷點(diǎn)擊觸發(fā)、監(jiān)聽滾動(dòng)事件。
13.函數(shù)珂里化
- 指的是將一個(gè)接受多個(gè)參數(shù)的函數(shù) 變?yōu)?nbsp;接受一個(gè)參數(shù)返回一個(gè)函數(shù)的固定形式,這樣便于再次調(diào)用,例如f(1)(2)
經(jīng)典面試題:實(shí)現(xiàn)add(1)(2)(3)(4)=10; 、 add(1)(1,2,3)(2)=9;
- function add() {
- const _args = [...arguments];
- function fn() {
- _args.push(...arguments);
- return fn;
- }
- fn.toString = function() {
- return _args.reduce((sum, cur) => sum + cur);
- }
- return fn;
- }
- 復(fù)制代碼
14.模擬new操作
3個(gè)步驟:
- 以ctor.prototype為原型創(chuàng)建一個(gè)對(duì)象。
- 執(zhí)行構(gòu)造函數(shù)并將this綁定到新創(chuàng)建的對(duì)象上。
- 判斷構(gòu)造函數(shù)執(zhí)行返回的結(jié)果是否是引用數(shù)據(jù)類型,若是則返回構(gòu)造函數(shù)執(zhí)行的結(jié)果,否則返回創(chuàng)建的對(duì)象。
- function newOperator(ctor, ...args) {
- if (typeof ctor !== 'function') {
- throw new TypeError('Type Error');
- }
- const obj = Object.create(ctor.prototype);
- const res = ctor.apply(obj, args);
- const isObject = typeof res === 'object' && res !== null;
- const isFunction = typeof res === 'function';
- return isObject || isFunction ? res : obj;
- }
- 復(fù)制代碼
15.instanceof
instanceof運(yùn)算符用于檢測(cè)構(gòu)造函數(shù)的prototype屬性是否出現(xiàn)在某個(gè)實(shí)例對(duì)象的原型鏈上。
- const myInstanceof = (left, right) => {
- // 基本數(shù)據(jù)類型都返回false
- if (typeof left !== 'object' || left === null) return false;
- let proto = Object.getPrototypeOf(left);
- while (true) {
- if (proto === null) return false;
- if (proto === right.prototype) return true;
- proto = Object.getPrototypeOf(proto);
- }
- }
- 復(fù)制代碼
16.原型繼承
這里只寫寄生組合繼承了,中間還有幾個(gè)演變過來的繼承但都有一些缺陷
- function Parent() {
- this.name = 'parent';
- }
- function Child() {
- Parent.call(this);
- this.type = 'children';
- }
- Child.prototype = Object.create(Parent.prototype);
- Child.prototype.constructor = Child;
- 復(fù)制代碼