前端js手寫題經(jīng)常忘,記錄一下

實(shí)現(xiàn)forEach方法

Array.prototype.myForEach = function(callback, context=window) {
  // this=>arr
  let self = this,  
      i = 0,
      len = self.length;

  for(;i<len;i++) {
    typeof callback == 'function' && callback.call(context,self[i], i)
   }
}

修改嵌套層級(jí)很深對(duì)象的 key

// 有一個(gè)嵌套層次很深的對(duì)象奴紧,key 都是 a_b 形式 ,需要改成 ab 的形式,注意不能用遞歸。

const a = {
  a_y: {
    a_z: {
      y_x: 6
    },
    b_c: 1
  }
}
// {
//   ay: {
//     az: {
//       yx: 6
//     },
//     bc: 1
//   }
// }

方法1:序列化 JSON.stringify + 正則匹配

const regularExpress = (obj) => {
  try {
    const str = JSON.stringify(obj).replace(/_/g, "");
    return JSON.parse(str);
  } catch (error) {
    return obj;
  }
};;

方法2:遞歸

const recursion = (obj) => {
  const keys = Object.keys(obj);
  keys.forEach((key) => {
    const newKey = key.replace(/_/g, "");
    obj[newKey] = recursion(obj[key]);
    delete obj[key];
  });
  return obj;
};

前端手寫面試題詳細(xì)解答

數(shù)組中的數(shù)據(jù)根據(jù)key去重

給定一個(gè)任意數(shù)組款违,實(shí)現(xiàn)一個(gè)通用函數(shù),讓數(shù)組中的數(shù)據(jù)根據(jù) key 排重:

const dedup = (data, getKey = () => {} ) => {
  // todo
}
let data = [
  { id: 1, v: 1 },
  { id: 2, v: 2 },
  { id: 1, v: 1 },
];

// 以 id 作為排重 key群凶,執(zhí)行函數(shù)得到結(jié)果
// data = [
//   { id: 1, v: 1 },
//   { id: 2, v: 2 },
// ];

實(shí)現(xiàn)

const dedup = (data, getKey = () => { }) => {
    const dateMap = data.reduce((pre, cur) => {
        const key = getKey(cur)
        if (!pre[key]) {
            pre[key] = cur
        }
        return pre
    }, {})
    return Object.values(dateMap)
}

使用

let data = [
    { id: 1, v: 1 },
    { id: 2, v: 2 },
    { id: 1, v: 1 },
];
console.log(dedup(data, (item) => item.id))

// 以 id 作為排重 key插爹,執(zhí)行函數(shù)得到結(jié)果
// data = [
//   { id: 1, v: 1 },
//   { id: 2, v: 2 },
// ];

前端手寫面試題詳細(xì)解答

實(shí)現(xiàn)節(jié)流函數(shù)(throttle)

節(jié)流函數(shù)原理:指頻繁觸發(fā)事件時(shí),只會(huì)在指定的時(shí)間段內(nèi)執(zhí)行事件回調(diào)请梢,即觸發(fā)事件間隔大于等于指定的時(shí)間才會(huì)執(zhí)行回調(diào)函數(shù)赠尾。總結(jié)起來就是: 事件溢陪,按照一段時(shí)間的間隔來進(jìn)行觸發(fā) 萍虽。

[圖片上傳失敗...(image-fc5918-1663910059294)]

像dom的拖拽睛廊,如果用消抖的話形真,就會(huì)出現(xiàn)卡頓的感覺,因?yàn)橹辉谕V沟臅r(shí)候執(zhí)行了一次超全,這個(gè)時(shí)候就應(yīng)該用節(jié)流咆霜,在一定時(shí)間內(nèi)多次執(zhí)行,會(huì)流暢很多

手寫簡(jiǎn)版

使用時(shí)間戳的節(jié)流函數(shù)會(huì)在第一次觸發(fā)事件時(shí)立即執(zhí)行嘶朱,以后每過 wait 秒之后才執(zhí)行一次蛾坯,并且最后一次觸發(fā)事件不會(huì)被執(zhí)行

時(shí)間戳方式:

// func是用戶傳入需要防抖的函數(shù)
// wait是等待時(shí)間
const throttle = (func, wait = 50) => {
  // 上一次執(zhí)行該函數(shù)的時(shí)間
  let lastTime = 0
  return function(...args) {
    // 當(dāng)前時(shí)間
    let now = +new Date()
    // 將當(dāng)前時(shí)間和上一次執(zhí)行函數(shù)時(shí)間對(duì)比
    // 如果差值大于設(shè)置的等待時(shí)間就執(zhí)行函數(shù)
    if (now - lastTime > wait) {
      lastTime = now
      func.apply(this, args)
    }
  }
}

setInterval(
  throttle(() => {
    console.log(1)
  }, 500),
  1
)

定時(shí)器方式:

使用定時(shí)器的節(jié)流函數(shù)在第一次觸發(fā)時(shí)不會(huì)執(zhí)行,而是在 delay 秒之后才執(zhí)行疏遏,當(dāng)最后一次停止觸發(fā)后脉课,還會(huì)再執(zhí)行一次函數(shù)

function throttle(func, delay){
  var timer = null;
  returnfunction(){
    var context = this;
    var args = arguments;
    if(!timer){
      timer = setTimeout(function(){
        func.apply(context, args);
        timer = null;
      },delay);
    }
  }
}

適用場(chǎng)景:

  • DOM 元素的拖拽功能實(shí)現(xiàn)(mousemove
  • 搜索聯(lián)想(keyup
  • 計(jì)算鼠標(biāo)移動(dòng)的距離(mousemove
  • Canvas 模擬畫板功能(mousemove
  • 監(jiān)聽滾動(dòng)事件判斷是否到頁面底部自動(dòng)加載更多
  • 拖拽場(chǎng)景:固定時(shí)間內(nèi)只執(zhí)行一次救军,防止超高頻次觸發(fā)位置變動(dòng)
  • 縮放場(chǎng)景:監(jiān)控瀏覽器resize
  • 動(dòng)畫場(chǎng)景:避免短時(shí)間內(nèi)多次觸發(fā)動(dòng)畫引起性能問題

總結(jié)

  • 函數(shù)防抖 :將幾次操作合并為一次操作進(jìn)行。原理是維護(hù)一個(gè)計(jì)時(shí)器倘零,規(guī)定在delay時(shí)間后觸發(fā)函數(shù)唱遭,但是在delay時(shí)間內(nèi)再次觸發(fā)的話,就會(huì)取消之前的計(jì)時(shí)器而重新設(shè)置呈驶。這樣一來拷泽,只有最后一次操作能被觸發(fā)。
  • 函數(shù)節(jié)流 :使得一定時(shí)間內(nèi)只觸發(fā)一次函數(shù)袖瞻。原理是通過判斷是否到達(dá)一定時(shí)間來觸發(fā)函數(shù)司致。

基于Generator函數(shù)實(shí)現(xiàn)async/await原理

核心:傳遞給我一個(gè)Generator函數(shù),把函數(shù)中的內(nèi)容基于Iterator迭代器的特點(diǎn)一步步的執(zhí)行

function readFile(file) {
    return new Promise(resolve => {
        setTimeout(() => {
            resolve(file);
    }, 1000);
    })
};

function asyncFunc(generator) {
    const iterator = generator(); // 接下來要執(zhí)行next
  // data為第一次執(zhí)行之后的返回結(jié)果聋迎,用于傳給第二次執(zhí)行
  const next = (data) => {
        let { value, done } = iterator.next(data); // 第二次執(zhí)行脂矫,并接收第一次的請(qǐng)求結(jié)果 data

    if (done) return; // 執(zhí)行完畢(到第三次)直接返回
    // 第一次執(zhí)行next時(shí),yield返回的 promise實(shí)例 賦值給了 value
    value.then(data => {
      next(data); // 當(dāng)?shù)谝淮蝪alue 執(zhí)行完畢且成功時(shí)霉晕,執(zhí)行下一步(并把第一次的結(jié)果傳遞下一步)
    });
  }
  next();
};

asyncFunc(function* () {
    // 生成器函數(shù):控制代碼一步步執(zhí)行 
  let data = yield readFile('a.js'); // 等這一步驟執(zhí)行執(zhí)行成功之后羹唠,再往下走,沒執(zhí)行完的時(shí)候娄昆,直接返回
  data = yield readFile(data + 'b.js');
  return data;
})

實(shí)現(xiàn)模板字符串解析功能

let template = '我是{{name}}佩微,年齡{{age}},性別{{sex}}';
let data = {
  name: '姓名',
  age: 18
}
render(template, data); // 我是姓名萌焰,年齡18哺眯,性別undefined
function render(template, data) {
  const reg = /\{\{(\w+)\}\}/; // 模板字符串正則
  if (reg.test(template)) { // 判斷模板里是否有模板字符串
    const name = reg.exec(template)[1]; // 查找當(dāng)前模板里第一個(gè)模板字符串的字段
    template = template.replace(reg, data[name]); // 將第一個(gè)模板字符串渲染
    return render(template, data); // 遞歸的渲染并返回渲染后的結(jié)構(gòu)
  }
  return template; // 如果模板沒有模板字符串直接返回
}

AJAX

const getJSON = function(url) {
  return new Promise((resolve, reject) => {
    const xhr = XMLHttpRequest ? new XMLHttpRequest() : new ActiveXObject('Mscrosoft.XMLHttp');
    xhr.open('GET', url, false);
    xhr.setRequestHeader('Accept', 'application/json');
    xhr.onreadystatechange = function() {
      if (xhr.readyState !== 4) return;
      if (xhr.status === 200 || xhr.status === 304) {
        resolve(xhr.responseText);
      } else {
        reject(new Error(xhr.responseText));
      }
    }
    xhr.send();
  })
}

實(shí)現(xiàn)雙向數(shù)據(jù)綁定

let obj = {}
let input = document.getElementById('input')
let span = document.getElementById('span')
// 數(shù)據(jù)劫持
Object.defineProperty(obj, 'text', {
  configurable: true,
  enumerable: true,
  get() {
    console.log('獲取數(shù)據(jù)了')
  },
  set(newVal) {
    console.log('數(shù)據(jù)更新了')
    input.value = newVal
    span.innerHTML = newVal
  }
})
// 輸入監(jiān)聽
input.addEventListener('keyup', function(e) {
  obj.text = e.target.value
})

實(shí)現(xiàn)數(shù)組的扁平化

(1)遞歸實(shí)現(xiàn)

普通的遞歸思路很容易理解,就是通過循環(huán)遞歸的方式扒俯,一項(xiàng)一項(xiàng)地去遍歷奶卓,如果每一項(xiàng)還是一個(gè)數(shù)組,那么就繼續(xù)往下遍歷撼玄,利用遞歸程序的方法夺姑,來實(shí)現(xiàn)數(shù)組的每一項(xiàng)的連接:

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 函數(shù)迭代

從上面普通的遞歸函數(shù)中可以看出掌猛,其實(shí)就是對(duì)數(shù)組的每一項(xiàng)進(jìn)行處理盏浙,那么其實(shí)也可以用reduce 來實(shí)現(xiàn)數(shù)組的拼接,從而簡(jiǎn)化第一種方法的代碼荔茬,改造后的代碼如下所示:

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)擴(kuò)展運(yùn)算符實(shí)現(xiàn)

這個(gè)方法的實(shí)現(xiàn),采用了擴(kuò)展運(yùn)算符和 some 的方法慕蔚,兩者共同使用丐黄,達(dá)到數(shù)組扁平化的目的:

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 兩個(gè)方法來共同實(shí)現(xiàn)數(shù)組扁平化孔飒,由于數(shù)組會(huì)默認(rèn)帶一個(gè) toString 的方法灌闺,所以可以把數(shù)組直接轉(zhuǎn)換成逗號(hào)分隔的字符串艰争,然后再用 split 方法把字符串重新轉(zhuǎn)換為數(shù)組,如下面的代碼所示:

let arr = [1, [2, [3, 4]]];
function flatten(arr) {
    return arr.toString().split(',');
}
console.log(flatten(arr)); //  [1, 2, 3, 4桂对,5]

通過這兩個(gè)方法可以將多維數(shù)組直接轉(zhuǎn)換成逗號(hào)連接的字符串园细,然后再重新分隔成數(shù)組。

(5)ES6 中的 flat

我們還可以直接調(diào)用 ES6 中的 flat 方法來實(shí)現(xiàn)數(shù)組扁平化接校。flat 方法的語法:arr.flat([depth])

其中 depth 是 flat 的參數(shù)猛频,depth 是可以傳遞數(shù)組的展開深度(默認(rèn)不填、數(shù)值是 1)蛛勉,即展開一層數(shù)組鹿寻。如果層數(shù)不確定,參數(shù)可以傳進(jìn) Infinity诽凌,代表不論多少層都要展開:

let arr = [1, [2, [3, 4]]];
function flatten(arr) {
  return arr.flat(Infinity);
}
console.log(flatten(arr)); //  [1, 2, 3, 4毡熏,5]

可以看出,一個(gè)嵌套了兩層的數(shù)組侣诵,通過將 flat 方法的參數(shù)設(shè)置為 Infinity痢法,達(dá)到了我們預(yù)期的效果。其實(shí)同樣也可以設(shè)置成 2杜顺,也能實(shí)現(xiàn)這樣的效果财搁。在編程過程中,如果數(shù)組的嵌套層數(shù)不確定躬络,最好直接使用 Infinity尖奔,可以達(dá)到扁平化。 (6)正則和 JSON 方法 在第4種方法中已經(jīng)使用 toString 方法穷当,其中仍然采用了將 JSON.stringify 的方法先轉(zhuǎn)換為字符串提茁,然后通過正則表達(dá)式過濾掉字符串中的數(shù)組的方括號(hào),最后再利用 JSON.parse 把它轉(zhuǎn)換成數(shù)組:

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]

實(shí)現(xiàn)apply方法

思路: 利用this的上下文特性茴扁。apply其實(shí)就是改一下參數(shù)的問題

Function.prototype.myApply = function(context = window, args) {
  // this-->func  context--> obj  args--> 傳遞過來的參數(shù)

  // 在context上加一個(gè)唯一值不影響context上的屬性
  let key = Symbol('key')
  context[key] = this; // context為調(diào)用的上下文,this此處為函數(shù),將這個(gè)函數(shù)作為context的方法
  // let args = [...arguments].slice(1)   //第一個(gè)參數(shù)為obj所以刪除,偽數(shù)組轉(zhuǎn)為數(shù)組

  let result = context[key](...args); // 這里和call傳參不一樣

  // 清除定義的this 不刪除會(huì)導(dǎo)致context屬性越來越多
  delete context[key]; 

  // 返回結(jié)果
  return result;
}
// 使用
function f(a,b){
 console.log(a,b)
 console.log(this.name)
}
let obj={
 name:'張三'
}
f.myApply(obj,[1,2])  //arguments[1]

數(shù)組去重方法匯總

首先:我知道多少種去重方式

1. 雙層 for 循環(huán)

function distinct(arr) {
    for (let i=0, len=arr.length; i<len; i++) {
        for (let j=i+1; j<len; j++) {
            if (arr[i] == arr[j]) {
                arr.splice(j, 1);
                // splice 會(huì)改變數(shù)組長(zhǎng)度汪疮,所以要將數(shù)組長(zhǎng)度 len 和下標(biāo) j 減一
                len--;
                j--;
            }
        }
    }
    return arr;
}

思想: 雙重 for 循環(huán)是比較笨拙的方法峭火,它實(shí)現(xiàn)的原理很簡(jiǎn)單:先定義一個(gè)包含原始數(shù)組第一個(gè)元素的數(shù)組,然后遍歷原始數(shù)組铲咨,將原始數(shù)組中的每個(gè)元素與新數(shù)組中的每個(gè)元素進(jìn)行比對(duì)躲胳,如果不重復(fù)則添加到新數(shù)組中,最后返回新數(shù)組纤勒;因?yàn)樗臅r(shí)間復(fù)雜度是O(n^2),如果數(shù)組長(zhǎng)度很大隆檀,效率會(huì)很低

2. Array.filter() 加 indexOf/includes

function distinct(a, b) {
    let arr = a.concat(b);
    return arr.filter((item, index)=> {
        //return arr.indexOf(item) === index
        return arr.includes(item)
    })
}

思想: 利用indexOf檢測(cè)元素在數(shù)組中第一次出現(xiàn)的位置是否和元素現(xiàn)在的位置相等摇天,如果不等則說明該元素是重復(fù)元素

3. ES6 中的 Set 去重

function distinct(array) {
   return Array.from(new Set(array));
}

思想: ES6 提供了新的數(shù)據(jù)結(jié)構(gòu) Set粹湃,Set 結(jié)構(gòu)的一個(gè)特性就是成員值都是唯一的,沒有重復(fù)的值泉坐。

4. reduce 實(shí)現(xiàn)對(duì)象數(shù)組去重復(fù)

var resources = [
    { name: "張三", age: "18" },
    { name: "張三", age: "19" },
    { name: "張三", age: "20" },
    { name: "李四", age: "19" },
    { name: "王五", age: "20" },
    { name: "趙六", age: "21" }
]
var temp = {};
resources = resources.reduce((prev, curv) => {
 // 如果臨時(shí)對(duì)象中有這個(gè)名字为鳄,什么都不做
 if (temp[curv.name]) {

 }else {
    // 如果臨時(shí)對(duì)象沒有就把這個(gè)名字加進(jìn)去,同時(shí)把當(dāng)前的這個(gè)對(duì)象加入到prev中
    temp[curv.name] = true;
    prev.push(curv);
 }
 return prev
}, []);
console.log("結(jié)果", resources);

這種方法是利用高階函數(shù) reduce 進(jìn)行去重腕让, 這里只需要注意initialValue得放一個(gè)空數(shù)組[]孤钦,不然沒法push

實(shí)現(xiàn) add(1)(2)(3)

函數(shù)柯里化概念: 柯里化(Currying)是把接受多個(gè)參數(shù)的函數(shù)轉(zhuǎn)變?yōu)榻邮芤粋€(gè)單一參數(shù)的函數(shù),并且返回接受余下的參數(shù)且返回結(jié)果的新函數(shù)的技術(shù)纯丸。

1)粗暴版

function add (a) {
return function (b) {
     return function (c) {
      return a + b + c;
     }
}
}
console.log(add(1)(2)(3)); // 6

2)柯里化解決方案

  • 參數(shù)長(zhǎng)度固定
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)); // 12
console.log(add(3)(6)(9)(25)); // 43

對(duì)于add(3)(4)(5)偏形,其執(zhí)行過程如下:

  1. 先執(zhí)行add(3),此時(shí)m=3觉鼻,并且返回temp函數(shù)俊扭;

  2. 執(zhí)行temp(4),這個(gè)函數(shù)內(nèi)執(zhí)行add(m+n)坠陈,n是此次傳進(jìn)來的數(shù)值4萨惑,m值還是上一步中的3,所以add(m+n)=add(3+4)=add(7)仇矾,此時(shí)m=7庸蔼,并且返回temp函數(shù)

  3. 執(zhí)行temp(5),這個(gè)函數(shù)內(nèi)執(zhí)行add(m+n)贮匕,n是此次傳進(jìn)來的數(shù)值5朱嘴,m值還是上一步中的7,所以add(m+n)=add(7+5)=add(12)粗合,此時(shí)m=12萍嬉,并且返回temp函數(shù)

  4. 由于后面沒有傳入?yún)?shù),等于返回的temp函數(shù)不被執(zhí)行而是打印隙疚,了解JS的朋友都知道對(duì)象的toString是修改對(duì)象轉(zhuǎn)換字符串的方法壤追,因此代碼中temp函數(shù)的toString函數(shù)return m值,而m值是最后一步執(zhí)行函數(shù)時(shí)的值m=12供屉,所以返回值是12行冰。

  • 參數(shù)長(zhǎng)度不固定
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 = [] //保證再次調(diào)用時(shí)清空
            return val
        }
    }
}
let addCurry = currying(add)
console.log(addCurry(1)(2)(3)(4, 5)())  //15
console.log(addCurry(1)(2)(3, 4, 5)())  //15
console.log(addCurry(1)(2, 3, 4, 5)())  //15

實(shí)現(xiàn)some方法

Array.prototype.mySome=function(callback, context = window){
             var len = this.length,
                 flag=false,
           i = 0;

             for(;i < len; i++){
                if(callback.apply(context, [this[i], i , this])){
                    flag=true;
                    break;
                } 
             }
             return flag;
        }

        // var flag=arr.mySome((v,index,arr)=>v.num>=10,obj)
        // console.log(flag);

使用 setTimeout 實(shí)現(xiàn) setInterval

setInterval 的作用是每隔一段指定時(shí)間執(zhí)行一個(gè)函數(shù),但是這個(gè)執(zhí)行不是真的到了時(shí)間立即執(zhí)行伶丐,它真正的作用是每隔一段時(shí)間將事件加入事件隊(duì)列中去悼做,只有當(dāng)當(dāng)前的執(zhí)行棧為空的時(shí)候,才能去從事件隊(duì)列中取出事件執(zhí)行哗魂。所以可能會(huì)出現(xiàn)這樣的情況肛走,就是當(dāng)前執(zhí)行棧執(zhí)行的時(shí)間很長(zhǎng),導(dǎo)致事件隊(duì)列里邊積累多個(gè)定時(shí)器加入的事件录别,當(dāng)執(zhí)行棧結(jié)束的時(shí)候朽色,這些事件會(huì)依次執(zhí)行邻吞,因此就不能到間隔一段時(shí)間執(zhí)行的效果。

針對(duì) setInterval 的這個(gè)缺點(diǎn)葫男,我們可以使用 setTimeout 遞歸調(diào)用來模擬 setInterval抱冷,這樣我們就確保了只有一個(gè)事件結(jié)束了,我們才會(huì)觸發(fā)下一個(gè)定時(shí)器事件梢褐,這樣解決了 setInterval 的問題旺遮。

實(shí)現(xiàn)思路是使用遞歸函數(shù),不斷地去執(zhí)行 setTimeout 從而達(dá)到 setInterval 的效果

function mySetInterval(fn, timeout) {
  // 控制器盈咳,控制定時(shí)器是否繼續(xù)執(zhí)行
  var timer = {
    flag: true
  };
  // 設(shè)置遞歸函數(shù)耿眉,模擬定時(shí)器執(zhí)行。
  function interval() {
    if (timer.flag) {
      fn();
      setTimeout(interval, timeout);
    }
  }
  // 啟動(dòng)定時(shí)器
  setTimeout(interval, timeout);
  // 返回控制器
  return timer;
}

實(shí)現(xiàn)new的過程

new操作符做了這些事:

  • 創(chuàng)建一個(gè)全新的對(duì)象
  • 這個(gè)對(duì)象的__proto__要指向構(gòu)造函數(shù)的原型prototype
  • 執(zhí)行構(gòu)造函數(shù)猪贪,使用 call/apply 改變 this 的指向
  • 返回值為object類型則作為new方法的返回值返回跷敬,否則返回上述全新對(duì)象
function myNew(fn, ...args) {
  // 基于原型鏈 創(chuàng)建一個(gè)新對(duì)象
  let newObj = Object.create(fn.prototype);
  // 添加屬性到新對(duì)象上 并獲取obj函數(shù)的結(jié)果
  let res = fn.apply(newObj, args); // 改變this指向

  // 如果執(zhí)行結(jié)果有返回值并且是一個(gè)對(duì)象, 返回執(zhí)行的結(jié)果, 否則, 返回新創(chuàng)建的對(duì)象
  return typeof res === 'object' ? res: newObj;
}
// 用法
function Person(name, age) {
  this.name = name;
  this.age = age;
}
Person.prototype.say = function() {
  console.log(this.age);
};
let p1 = myNew(Person, "poety", 18);
console.log(p1.name);
console.log(p1);
p1.say();

實(shí)現(xiàn)apply方法

apply原理與call很相似,不多贅述

// 模擬 apply
Function.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;
};

實(shí)現(xiàn)防抖函數(shù)(debounce)

防抖函數(shù)原理:在事件被觸發(fā)n秒后再執(zhí)行回調(diào)热押,如果在這n秒內(nèi)又被觸發(fā)西傀,則重新計(jì)時(shí)。

那么與節(jié)流函數(shù)的區(qū)別直接看這個(gè)動(dòng)畫實(shí)現(xiàn)即可桶癣。

手寫簡(jiǎn)化版:

// 防抖函數(shù)
const debounce = (fn, delay) => {
  let timer = null;
  return (...args) => {
    clearTimeout(timer);
    timer = setTimeout(() => {
      fn.apply(this, args);
    }, delay);
  };
};

適用場(chǎng)景:

  • 按鈕提交場(chǎng)景:防止多次提交按鈕拥褂,只執(zhí)行最后提交的一次
  • 服務(wù)端驗(yàn)證場(chǎng)景:表單驗(yàn)證需要服務(wù)端配合,只執(zhí)行一段連續(xù)的輸入事件的最后一次牙寞,還有搜索聯(lián)想詞功能類似

生存環(huán)境請(qǐng)用lodash.debounce

實(shí)現(xiàn)call方法

call做了什么:

  • 將函數(shù)設(shè)為對(duì)象的屬性
  • 執(zhí)行和刪除這個(gè)函數(shù)
  • 指定this到函數(shù)并傳入給定參數(shù)執(zhí)行函數(shù)
  • 如果不傳入?yún)?shù)饺鹃,默認(rèn)指向?yàn)?window
// 模擬 call bar.mycall(null);
//實(shí)現(xiàn)一個(gè)call方法:
// 原理:利用 context.xxx = self obj.xx = func-->obj.xx()
Function.prototype.myCall = function(context = window, ...args) {
  if (typeof this !== "function") {
    throw new Error('type error')
  }
  // this-->func  context--> obj  args--> 傳遞過來的參數(shù)

  // 在context上加一個(gè)唯一值不影響context上的屬性
  let key = Symbol('key')
  context[key] = this; // context為調(diào)用的上下文,this此處為函數(shù),將這個(gè)函數(shù)作為context的方法
  // let args = [...arguments].slice(1)   //第一個(gè)參數(shù)為obj所以刪除,偽數(shù)組轉(zhuǎn)為數(shù)組

  // 綁定參數(shù) 并執(zhí)行函數(shù)
  let result = context[key](...args);
  // 清除定義的this 不刪除會(huì)導(dǎo)致context屬性越來越多
  delete context[key];

  // 返回結(jié)果 
  return result;
};
//用法:f.call(obj,arg1)
function f(a,b){
 console.log(a+b)
 console.log(this.name)
}
let obj={
 name:1
}
f.myCall(obj,1,2) //否則this指向window

setTimeout與setInterval實(shí)現(xiàn)

setTimeout 模擬實(shí)現(xiàn) setInterval

題目描述: setInterval 用來實(shí)現(xiàn)循環(huán)定時(shí)調(diào)用 可能會(huì)存在一定的問題 能用 setTimeout 解決嗎

實(shí)現(xiàn)代碼如下:

function mySetInterval(fn, t) {
  let timerId = null;
  function interval() {
    fn();
    timerId = setTimeout(interval, t); // 遞歸調(diào)用
  }
  timerId = setTimeout(interval, t); // 首次調(diào)用
  return {
    // 利用閉包的特性 保存timerId
    cancel:() => {
      clearTimeout(timerId)
    }
  }
}
// 測(cè)試
var a = mySetInterval(()=>{
  console.log(111);
},1000)
var b = mySetInterval(() => {
  console.log(222)
}, 1000)

// 終止定時(shí)器
a.cancel()
b.cancel()

為什么要用 setTimeout 模擬實(shí)現(xiàn) setInterval间雀?setInterval 的缺陷是什么悔详?

setInterval(fn(), N);

上面這句代碼的意思其實(shí)是fn()將會(huì)在 N 秒之后被推入任務(wù)隊(duì)列。在 setInterval 被推入任務(wù)隊(duì)列時(shí)惹挟,如果在它前面有很多任務(wù)或者某個(gè)任務(wù)等待時(shí)間較長(zhǎng)比如網(wǎng)絡(luò)請(qǐng)求等茄螃,那么這個(gè)定時(shí)器的執(zhí)行時(shí)間和我們預(yù)定它執(zhí)行的時(shí)間可能并不一致

// 最常見的出現(xiàn)的就是,當(dāng)我們需要使用 ajax 輪詢服務(wù)器是否有新數(shù)據(jù)時(shí)连锯,必定會(huì)有一些人會(huì)使用 setInterval归苍,然而無論網(wǎng)絡(luò)狀況如何,它都會(huì)去一遍又一遍的發(fā)送請(qǐng)求运怖,最后的間隔時(shí)間可能和原定的時(shí)間有很大的出入

// 做一個(gè)網(wǎng)絡(luò)輪詢拼弃,每一秒查詢一次數(shù)據(jù)。
let startTime = new Date().getTime();
let count = 0;

setInterval(() => {
    let i = 0;
    while (i++ < 10000000); // 假設(shè)的網(wǎng)絡(luò)延遲
    count++;
    console.log(
        "與原設(shè)定的間隔時(shí)差了:",
        new Date().getTime() - (startTime + count * 1000),
        "毫秒"
    );
}, 1000)

// 輸出:
// 與原設(shè)定的間隔時(shí)差了: 567 毫秒
// 與原設(shè)定的間隔時(shí)差了: 552 毫秒
// 與原設(shè)定的間隔時(shí)差了: 563 毫秒
// 與原設(shè)定的間隔時(shí)差了: 554 毫秒(2次)
// 與原設(shè)定的間隔時(shí)差了: 564 毫秒
// 與原設(shè)定的間隔時(shí)差了: 602 毫秒
// 與原設(shè)定的間隔時(shí)差了: 573 毫秒
// 與原設(shè)定的間隔時(shí)差了: 633 毫秒

再次強(qiáng)調(diào) 摇展,定時(shí)器指定的時(shí)間間隔吻氧,表示的是何時(shí)將定時(shí)器的代碼添加到消息隊(duì)列,而不是何時(shí)執(zhí)行代碼。所以真正何時(shí)執(zhí)行代碼的時(shí)間是不能保證的医男,取決于何時(shí)被主線程的事件循環(huán)取到砸狞,并執(zhí)行捻勉。

setInterval(function, N)
//即:每隔N秒把function事件推到消息隊(duì)列中

[圖片上傳失敗...(image-9ef7ae-1663910059294)]

上圖可見镀梭,setInterval 每隔 100ms 往隊(duì)列中添加一個(gè)事件;100ms 后踱启,添加 T1 定時(shí)器代碼至隊(duì)列中报账,主線程中還有任務(wù)在執(zhí)行,所以等待埠偿,some event 執(zhí)行結(jié)束后執(zhí)行 T1定時(shí)器代碼透罢;又過了 100msT2 定時(shí)器被添加到隊(duì)列中冠蒋,主線程還在執(zhí)行 T1 代碼羽圃,所以等待;又過了 100ms抖剿,理論上又要往隊(duì)列里推一個(gè)定時(shí)器代碼朽寞,但由于此時(shí) T2 還在隊(duì)列中,所以 T3 不會(huì)被添加(T3 被跳過)斩郎,結(jié)果就是此時(shí)被跳過脑融;這里我們可以看到,T1 定時(shí)器執(zhí)行結(jié)束后馬上執(zhí)行了 T2 代碼缩宜,所以并沒有達(dá)到定時(shí)器的效果

setInterval有兩個(gè)缺點(diǎn)

  • 使用setInterval時(shí)肘迎,某些間隔會(huì)被跳過
  • 可能多個(gè)定時(shí)器會(huì)連續(xù)執(zhí)行

可以這么理解 :每個(gè)setTimeout產(chǎn)生的任務(wù)會(huì)直接push到任務(wù)隊(duì)列中;而setInterval在每次把任務(wù)push到任務(wù)隊(duì)列前锻煌,都要進(jìn)行一下判斷(看上次的任務(wù)是否仍在隊(duì)列中)妓布。因而我們一般用setTimeout模擬setInterval,來規(guī)避掉上面的缺點(diǎn)

setInterval 模擬實(shí)現(xiàn) setTimeout

const mySetTimeout = (fn, t) => {
  const timer = setInterval(() => {
    clearInterval(timer);
    fn();
  }, t);
};
// 測(cè)試
// mySetTimeout(()=>{
//   console.log(1);
// },1000)

驗(yàn)證是否是身份證

function isCardNo(number) {
    var regx = /(^\d{15}$)|(^\d{18}$)|(^\d{17}(\d|X|x)$)/;
    return regx.test(number);
}


最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末宋梧,一起剝皮案震驚了整個(gè)濱河市匣沼,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌乃秀,老刑警劉巖肛著,帶你破解...
    沈念sama閱讀 211,194評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異跺讯,居然都是意外死亡枢贿,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,058評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門刀脏,熙熙樓的掌柜王于貴愁眉苦臉地迎上來局荚,“玉大人,你說我怎么就攤上這事∫” “怎么了轮傍?”我有些...
    開封第一講書人閱讀 156,780評(píng)論 0 346
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)首装。 經(jīng)常有香客問我创夜,道長(zhǎng),這世上最難降的妖魔是什么仙逻? 我笑而不...
    開封第一講書人閱讀 56,388評(píng)論 1 283
  • 正文 為了忘掉前任驰吓,我火速辦了婚禮,結(jié)果婚禮上系奉,老公的妹妹穿的比我還像新娘檬贰。我一直安慰自己,他們只是感情好缺亮,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,430評(píng)論 5 384
  • 文/花漫 我一把揭開白布翁涤。 她就那樣靜靜地躺著,像睡著了一般萌踱。 火紅的嫁衣襯著肌膚如雪葵礼。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,764評(píng)論 1 290
  • 那天虫蝶,我揣著相機(jī)與錄音章咧,去河邊找鬼。 笑死能真,一個(gè)胖子當(dāng)著我的面吹牛赁严,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播粉铐,決...
    沈念sama閱讀 38,907評(píng)論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼疼约,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了蝙泼?” 一聲冷哼從身側(cè)響起程剥,我...
    開封第一講書人閱讀 37,679評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎汤踏,沒想到半個(gè)月后织鲸,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,122評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡溪胶,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,459評(píng)論 2 325
  • 正文 我和宋清朗相戀三年搂擦,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片哗脖。...
    茶點(diǎn)故事閱讀 38,605評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡瀑踢,死狀恐怖扳还,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情橱夭,我是刑警寧澤氨距,帶...
    沈念sama閱讀 34,270評(píng)論 4 329
  • 正文 年R本政府宣布,位于F島的核電站棘劣,受9級(jí)特大地震影響俏让,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜呈础,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,867評(píng)論 3 312
  • 文/蒙蒙 一舆驶、第九天 我趴在偏房一處隱蔽的房頂上張望橱健。 院中可真熱鬧而钞,春花似錦、人聲如沸拘荡。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,734評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽珊皿。三九已至网缝,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間蟋定,已是汗流浹背粉臊。 一陣腳步聲響...
    開封第一講書人閱讀 31,961評(píng)論 1 265
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留驶兜,地道東北人扼仲。 一個(gè)月前我還...
    沈念sama閱讀 46,297評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像抄淑,于是被迫代替她去往敵國(guó)和親屠凶。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,472評(píng)論 2 348