<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          32個手寫JS,鞏固你的JS基礎(chǔ)(面試高頻)

          共 22043字,需瀏覽 45分鐘

           ·

          2020-10-26 04:10

          來源 | https://juejin.im/post/6875152247714480136

          作為前端開發(fā),JS是重中之重,最近結(jié)束了面試的高峰期,基本上offer也定下來了就等開獎,趁著這個時間總結(jié)下32個手寫JS問題,這些都是高頻面試題,希望對你能有所幫助。

          關(guān)于源碼都緊遵規(guī)范,都可跑通MDN示例,其余的大多會涉及一些關(guān)于JS的應(yīng)用題和本人面試過程

          01、數(shù)組扁平化

          數(shù)組扁平化是指將一個多維數(shù)組變?yōu)橐粋€一維數(shù)組

          const arr = [1, [2, [3, [4, 5]]], 6];// => [1, 2, 3, 4, 5, 6]

          方法一:使用flat()

          const res1 = arr.flat(Infinity);

          方法二:利用正則

          const res2 = JSON.stringify(arr).replace(/\[|\]/g, '').split(',');

          但數(shù)據(jù)類型都會變?yōu)樽址?/span>

          方法三:正則改良版本

          const res3 = JSON.parse('[' + JSON.stringify(arr).replace(/\[|\]/g, '') + ']');

          方法四:使用reduce

          const flatten = arr => {  return arr.reduce((pre, cur) => {    return pre.concat(Array.isArray(cur) ? flatten(cur) : cur);  }, [])}const res4 = flatten(arr);

          方法五:函數(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);

          02、數(shù)組去重

          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循環(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);        // 每刪除一個樹,j--保證j的值經(jīng)過自加后不變。同時,len--,減少循環(huán)次數(shù)提升性能        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;}

          當(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;}

          方法五:利用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;}

          03、類數(shù)組轉(zhuǎn)化為數(shù)組

          類數(shù)組是具有length屬性,但不具有數(shù)組原型上的方法。常見的類數(shù)組有arguments、DOM操作方法返回的結(jié)果。

          方法一: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'));

          04、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成為回調(diào)函數(shù)的對象傳遞(強制轉(zhuǎn)換對象)  const O = Object(this);  // >>>0 保證len為number,且為正整數(shù)  const len = O.length >>> 0;  for (let i = 0; i < len; i++) {    // 檢查i是否在O的屬性(會檢查原型鏈)    if (i in O) {      // 回調(diào)函數(shù)調(diào)用傳參      if (callback.call(thisArg, O[i], i, O)) {        res.push(O[i]);      }    }  }  return res;}

          對于>>>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;}

          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++;  }}

          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;  // 如果第二個參數(shù)為undefined的情況下  // 則數(shù)組的第一個有效值作為累加器的初始值  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;}

          08、Function.prototype.apply()

          第一個參數(shù)是綁定的this,默認(rèn)為window,第二個參數(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;}

          09、Function.prototype.call

          call唯一不同的是,call()方法接受的是一個參數(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 = context[fn](...args); delete context[fn]; return res;}

          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]) }}

          11、debounce(防抖)

          觸發(fā)高頻時間后n秒內(nèi)函數(shù)只會執(zhí)行一次,如果n秒內(nèi)高頻時間再次觸發(fā),則重新計算時間。

          const debounce = (fn, time) => {  let timeout = null;  return function() {    clearTimeout(timeout)    timeout = setTimeout(() => {      fn.apply(this, arguments);    }, time);  }};

          防抖常應(yīng)用于用戶進行搜索輸入節(jié)約請求資源,window觸發(fā)resize事件時進行防抖只觸發(fā)一次。

          12、throttle(節(jié)流)

          高頻時間觸發(fā),但n秒內(nèi)只會執(zhí)行一次,所以節(jié)流會稀釋函數(shù)的執(zhí)行頻率。

          const throttle = (fn, time) => {  let flag = true;  return function() {    if (!flag) return;    flag = false;    setTimeout(() => {      fn.apply(this, arguments);      flag = true;    }, time);  }}

          節(jié)流常應(yīng)用于鼠標(biāo)不斷點擊觸發(fā)、監(jiān)聽滾動事件。

          13、函數(shù)珂里化

          指的是將一個接受多個參數(shù)的函數(shù) 變?yōu)?接受一個參數(shù)返回一個函數(shù)的固定形式,這樣便于再次調(diào)用,例如f(1)(2)

          經(jīng)典面試題:實現(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;}

          14、模擬new操作

          3個步驟:

          1. ctor.prototype為原型創(chuàng)建一個對象。

          2. 執(zhí)行構(gòu)造函數(shù)并將this綁定到新創(chuàng)建的對象上。

          3. 判斷構(gòu)造函數(shù)執(zhí)行返回的結(jié)果是否是引用數(shù)據(jù)類型,若是則返回構(gòu)造函數(shù)執(zhí)行的結(jié)果,否則返回創(chuàng)建的對象。

          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;}

          15、instanceof

          instanceof運算符用于檢測構(gòu)造函數(shù)的prototype屬性是否出現(xiàn)在某個實例對象的原型鏈上。

          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);  }}

          16、原型繼承

          這里只寫寄生組合繼承了,中間還有幾個演變過來的繼承但都有一些缺陷

          function Parent() {  this.name = 'parent';}function Child() {  Parent.call(this);  this.type = 'children';}Child.prototype = Object.create(Parent.prototype);Child.prototype.constructor = Child;

          17、Object.is

          Object.is解決的主要是這兩個問題:

          +0 === -0  // trueNaN === NaN // false
          const is= (x, y) => {  if (x === y) {    // +0和-0應(yīng)該不相等    return x !== 0 || y !== 0 || 1/x === 1/y;  } else {    return x !== x && y !== y;  }}

          18、Object.assign

          Object.assign()方法用于將所有可枚舉屬性的值從一個或多個源對象復(fù)制到目標(biāo)對象。它將返回目標(biāo)對象(請注意這個操作是淺拷貝)

          Object.defineProperty(Object, 'assign', {  value: function(target, ...args) {    if (target == null) {      return new TypeError('Cannot convert undefined or null to object');    }        // 目標(biāo)對象需要統(tǒng)一是引用數(shù)據(jù)類型,若不是會自動轉(zhuǎn)換    const to = Object(target);
          for (let i = 0; i < args.length; i++) { // 每一個源對象 const nextSource = args[i]; if (nextSource !== null) { // 使用for...in和hasOwnProperty雙重判斷,確保只拿到本身的屬性、方法(不包含繼承的) for (const nextKey in nextSource) { if (Object.prototype.hasOwnProperty.call(nextSource, nextKey)) { to[nextKey] = nextSource[nextKey]; } } } } return to; }, // 不可枚舉 enumerable: false, writable: true, configurable: true,})

          19、深拷貝

          遞歸的完整版本(考慮到了Symbol屬性):

          const cloneDeep1 = (target, hash = new WeakMap()) => {  // 對于傳入?yún)?shù)處理  if (typeof target !== 'object' || target === null) {    return target;  }  // 哈希表中存在直接返回  if (hash.has(target)) return hash.get(target);
          const cloneTarget = Array.isArray(target) ? [] : {}; hash.set(target, cloneTarget);
          // 針對Symbol屬性 const symKeys = Object.getOwnPropertySymbols(target); if (symKeys.length) { symKeys.forEach(symKey => { if (typeof target[symKey] === 'object' && target[symKey] !== null) { cloneTarget[symKey] = cloneDeep1(target[symKey]); } else { cloneTarget[symKey] = target[symKey]; } }) }
          for (const i in target) { if (Object.prototype.hasOwnProperty.call(target, i)) { cloneTarget[i] = typeof target[i] === 'object' && target[i] !== null ? cloneDeep1(target[i], hash) : target[i]; } } return cloneTarget;}

          20、Promise

          實現(xiàn)思路:Promise源碼實現(xiàn)

          // 模擬實現(xiàn)Promise// Promise利用三大手段解決回調(diào)地獄:// 1. 回調(diào)函數(shù)延遲綁定// 2. 返回值穿透// 3. 錯誤冒泡
          // 定義三種狀態(tài)const PENDING = 'PENDING'; // 進行中const FULFILLED = 'FULFILLED'; // 已成功const REJECTED = 'REJECTED'; // 已失敗
          class Promise { constructor(exector) { // 初始化狀態(tài) this.status = PENDING; // 將成功、失敗結(jié)果放在this上,便于then、catch訪問 this.value = undefined; this.reason = undefined; // 成功態(tài)回調(diào)函數(shù)隊列 this.onFulfilledCallbacks = []; // 失敗態(tài)回調(diào)函數(shù)隊列 this.onRejectedCallbacks = [];
          const resolve = value => { // 只有進行中狀態(tài)才能更改狀態(tài) if (this.status === PENDING) { this.status = FULFILLED; this.value = value; // 成功態(tài)函數(shù)依次執(zhí)行 this.onFulfilledCallbacks.forEach(fn => fn(this.value)); } } const reject = reason => { // 只有進行中狀態(tài)才能更改狀態(tài) if (this.status === PENDING) { this.status = REJECTED; this.reason = reason; // 失敗態(tài)函數(shù)依次執(zhí)行 this.onRejectedCallbacks.forEach(fn => fn(this.reason)) } } try { // 立即執(zhí)行executor // 把內(nèi)部的resolve和reject傳入executor,用戶可調(diào)用resolve和reject exector(resolve, reject); } catch(e) { // executor執(zhí)行出錯,將錯誤內(nèi)容reject拋出去 reject(e); } } then(onFulfilled, onRejected) { onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value; onRejected = typeof onRejected === 'function'? onRejected : reason => { throw new Error(reason instanceof Error ? reason.message : reason) } // 保存this const self = this; return new Promise((resolve, reject) => { if (self.status === PENDING) { self.onFulfilledCallbacks.push(() => { // try捕獲錯誤 try { // 模擬微任務(wù) setTimeout(() => { const result = onFulfilled(self.value); // 分兩種情況: // 1. 回調(diào)函數(shù)返回值是Promise,執(zhí)行then操作 // 2. 如果不是Promise,調(diào)用新Promise的resolve函數(shù) result instanceof Promise ? result.then(resolve, reject) : resolve(result); }) } catch(e) { reject(e); } }); self.onRejectedCallbacks.push(() => { // 以下同理 try { setTimeout(() => { const result = onRejected(self.reason); // 不同點:此時是reject result instanceof Promise ? result.then(resolve, reject) : reject(result); }) } catch(e) { reject(e); } }) } else if (self.status === FULFILLED) { try { setTimeout(() => { const result = onFulfilled(self.value); result instanceof Promise ? result.then(resolve, reject) : resolve(result); }); } catch(e) { reject(e); } } else if (self.status === REJECTED) { try { setTimeout(() => { const result = onRejected(self.reason); result instanceof Promise ? result.then(resolve, reject) : resolve(result); }) } catch(e) { reject(e); } } }); } catch(onRejected) { return this.then(null, onRejected); } static resolve(value) { if (value instanceof Promise) { // 如果是Promise實例,直接返回 return value; } else { // 如果不是Promise實例,返回一個新的Promise對象,狀態(tài)為FULFILLED return new Promise((resolve, reject) => resolve(value)); } } static reject(reason) { return new Promise((resolve, reject) => { reject(reason); }) } static all(promiseArr) { const len = promiseArr.length; const values = new Array(len); // 記錄已經(jīng)成功執(zhí)行的promise個數(shù) let count = 0; return new Promise((resolve, reject) => { for (let i = 0; i < len; i++) { // Promise.resolve()處理,確保每一個都是promise實例 Promise.resolve(promiseArr[i]).then( val => { values[i] = val; count++; // 如果全部執(zhí)行完,返回promise的狀態(tài)就可以改變了 if (count === len) resolve(values); }, err => reject(err), ); } }) } static race(promiseArr) { return new Promise((resolve, reject) => { promiseArr.forEach(p => { Promise.resolve(p).then( val => resolve(val), err => reject(err), ) }) }) }}

          21、Promise.all

          Promise.all是支持鏈?zhǔn)秸{(diào)用的,本質(zhì)上就是返回了一個Promise實例,通過resolvereject來改變實例狀態(tài)。

          Promise.myAll = function(promiseArr) {  return new Promise((resolve, reject) => {    const ans = [];    let index = 0;    for (let i = 0; i < promiseArr.length; i++) {      promiseArr[i]      .then(res => {        ans[i] = res;        index++;        if (index === promiseArr.length) {          resolve(ans);        }      })      .catch(err => reject(err));    }  })}

          22、Promise.race

          Promise.race = function(promiseArr) {  return new Promise((resolve, reject) => {    promiseArr.forEach(p => {      // 如果不是Promise實例需要轉(zhuǎn)化為Promise實例      Promise.resolve(p).then(        val => resolve(val),        err => reject(err),      )    })  })}

          23、Promise并行限制

          就是實現(xiàn)有并行限制的Promise調(diào)度器問題。

          詳細(xì)實現(xiàn)思路:某條高頻面試原題:實現(xiàn)有并行限制的Promise調(diào)度器

          class Scheduler {  constructor() {    this.queue = [];    this.maxCount = 2;    this.runCounts = 0;  }  add(promiseCreator) {    this.queue.push(promiseCreator);  }  taskStart() {    for (let i = 0; i < this.maxCount; i++) {      this.request();    }  }  request() {    if (!this.queue || !this.queue.length || this.runCounts >= this.maxCount) {      return;    }    this.runCounts++;
          this.queue.shift()().then(() => { this.runCounts--; this.request(); }); }} const timeout = time => new Promise(resolve => { setTimeout(resolve, time);}) const scheduler = new Scheduler(); const addTask = (time,order) => { scheduler.add(() => timeout(time).then(()=>console.log(order)))} addTask(1000, '1');addTask(500, '2');addTask(300, '3');addTask(400, '4');scheduler.taskStart()// 2// 3// 1//?4

          24、JSONP

          script標(biāo)簽不遵循同源協(xié)議,可以用來進行跨域請求,優(yōu)點就是兼容性好但僅限于GET請求

          const jsonp = ({ url, params, callbackName }) => {  const generateUrl = () => {    let dataSrc = '';    for (let key in params) {      if (Object.prototype.hasOwnProperty.call(params, key)) {        dataSrc += `${key}=${params[key]}&`;      }    }    dataSrc += `callback=${callbackName}`;    return `${url}?${dataSrc}`;  }  return new Promise((resolve, reject) => {    const scriptEle = document.createElement('script');    scriptEle.src = generateUrl();    document.body.appendChild(scriptEle);    window[callbackName] = data => {      resolve(data);      document.removeChild(scriptEle);    }  })}

          25、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();  })}

          26、event模塊

          實現(xiàn)node中回調(diào)函數(shù)的機制,node中回調(diào)函數(shù)其實是內(nèi)部使用了觀察者模式

          觀察者模式:定義了對象間一種一對多的依賴關(guān)系,當(dāng)目標(biāo)對象Subject發(fā)生改變時,所有依賴它的對象Observer都會得到通知。

          function EventEmitter() {  this.events = new Map();}
          // 需要實現(xiàn)的一些方法:// addListener、removeListener、once、removeAllListeners、emit
          // 模擬實現(xiàn)addlistener方法const wrapCallback = (fn, once = false) => ({ callback: fn, once });EventEmitter.prototype.addListener = function(type, fn, once = false) { const hanlder = this.events.get(type); if (!hanlder) { // 沒有type綁定事件 this.events.set(type, wrapCallback(fn, once)); } else if (hanlder && typeof hanlder.callback === 'function') { // 目前type事件只有一個回調(diào) this.events.set(type, [hanlder, wrapCallback(fn, once)]); } else { // 目前type事件數(shù)>=2 hanlder.push(wrapCallback(fn, once)); }}// 模擬實現(xiàn)removeListenerEventEmitter.prototype.removeListener = function(type, listener) { const hanlder = this.events.get(type); if (!hanlder) return; if (!Array.isArray(this.events)) { if (hanlder.callback === listener.callback) this.events.delete(type); else return; } for (let i = 0; i < hanlder.length; i++) { const item = hanlder[i]; if (item.callback === listener.callback) { hanlder.splice(i, 1); i--; if (hanlder.length === 1) { this.events.set(type, hanlder[0]); } } }}// 模擬實現(xiàn)once方法EventEmitter.prototype.once = function(type, listener) { this.addListener(type, listener, true);}// 模擬實現(xiàn)emit方法EventEmitter.prototype.emit = function(type, ...args) { const hanlder = this.events.get(type); if (!hanlder) return; if (Array.isArray(hanlder)) { hanlder.forEach(item => { item.callback.apply(this, args); if (item.once) { this.removeListener(type, item); } }) } else { hanlder.callback.apply(this, args); if (hanlder.once) { this.events.delete(type); } } return true;}EventEmitter.prototype.removeAllListeners = function(type) { const hanlder = this.events.get(type); if (!hanlder) return; this.events.delete(type);}

          27、圖片懶加載

          可以給img標(biāo)簽統(tǒng)一自定義屬性src='default.png',當(dāng)檢測到圖片出現(xiàn)在窗口之后再補充src屬性,此時才會進行圖片資源加載。

          function lazyload() {  const imgs = document.getElementsByTagName('img');  const len = imgs.length;  // 視口的高度  const viewHeight = document.documentElement.clientHeight;  // 滾動條高度  const scrollHeight = document.documentElement.scrollTop || document.body.scrollTop;  for (let i = 0; i < len; i++) {    const offsetHeight = imgs[i].offsetTop;    if (offsetHeight < viewHeight + scrollHeight) {      const src = imgs[i].dataset.src;      imgs[i].src = src;    }  }}
          // 可以使用節(jié)流優(yōu)化一下window.addEventListener('scroll', lazyload);

          28、滾動加載

          原理就是監(jiān)聽頁面滾動事件,分析clientHeightscrollTopscrollHeight三者的屬性關(guān)系。

          window.addEventListener('scroll', function() {  const clientHeight = document.documentElement.clientHeight;  const scrollTop = document.documentElement.scrollTop;  const scrollHeight = document.documentElement.scrollHeight;  if (clientHeight + scrollTop >= scrollHeight) {    // 檢測到滾動至頁面底部,進行后續(xù)操作    // ...  }}, false);

          一個Demo:頁面滾動加載的Demo

          29、渲染幾萬條數(shù)據(jù)不卡住頁面

          渲染大數(shù)據(jù)時,合理使用createDocumentFragmentrequestAnimationFrame,將操作切分為一小段一小段執(zhí)行。

          setTimeout(() => {  // 插入十萬條數(shù)據(jù)  const total = 100000;  // 一次插入的數(shù)據(jù)  const once = 20;  // 插入數(shù)據(jù)需要的次數(shù)  const loopCount = Math.ceil(total / once);  let countOfRender = 0;  const ul = document.querySelector('ul');  // 添加數(shù)據(jù)的方法  function add() {    const fragment = document.createDocumentFragment();    for(let i = 0; i < once; i++) {      const li = document.createElement('li');      li.innerText = Math.floor(Math.random() * total);      fragment.appendChild(li);    }    ul.appendChild(fragment);    countOfRender += 1;    loop();  }  function loop() {    if(countOfRender < loopCount) {      window.requestAnimationFrame(add);    }  }  loop();}, 0)

          30、打印出當(dāng)前網(wǎng)頁使用了多少種HTML元素

          一行代碼可以解決:

          const fn = () => {  return [...new Set([...document.querySelectorAll('*')].map(el => el.tagName))].length;}

          值得注意的是:DOM操作返回的是類數(shù)組,需要轉(zhuǎn)換為數(shù)組之后才可以調(diào)用數(shù)組的方法。

          31、將VirtualDom轉(zhuǎn)化為真實DOM結(jié)構(gòu)

          這是當(dāng)前SPA應(yīng)用的核心概念之一

          // vnode結(jié)構(gòu):// {//   tag,//   attrs,//   children,// }
          //Virtual DOM => DOMfunction render(vnode, container) { container.appendChild(_render(vnode));}function _render(vnode) { // 如果是數(shù)字類型轉(zhuǎn)化為字符串 if (typeof vnode === 'number') { vnode = String(vnode); } // 字符串類型直接就是文本節(jié)點 if (typeof vnode === 'string') { return document.createTextNode(vnode); } // 普通DOM const dom = document.createElement(vnode.tag); if (vnode.attrs) { // 遍歷屬性 Object.keys(vnode.attrs).forEach(key => { const value = vnode.attrs[key]; dom.setAttribute(key, value); }) } // 子數(shù)組進行遞歸操作 vnode.children.forEach(child => render(child, dom)); return dom;}

          32、字符串解析問題

          var a = {    b: 123,    c: '456',    e: '789',}var str=`a{a.b}aa{a.c}aa {a.d}aaaa`;// => 'a123aa456aa {a.d}aaaa'

          實現(xiàn)函數(shù)使得將str字符串中的{}內(nèi)的變量替換,如果屬性不存在保持原樣(比如{a.d}

          類似于模版字符串,但有一點出入,實際上原理大差不差

          const fn1 = (str, obj) => {    let res = '';    // 標(biāo)志位,標(biāo)志前面是否有{    let flag = false;    let start;    for (let i = 0; i < str.length; i++) {        if (str[i] === '{') {            flag = true;            start = i + 1;            continue;        }        if (!flag) res += str[i];        else {            if (str[i] === '}') {                flag = false;                res += match(str.slice(start, i), obj);            }        }    }    return res;}// 對象匹配操作const match = (str, obj) => {    const keys = str.split('.').slice(1);    let index = 0;    let o = obj;    while (index < keys.length) {        const key = keys[index];        if (!o[key]) {            return `{${str}}`;        } else {            o = o[key];        }        index++;    }    return o;}

          本文完~


          瀏覽 130
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  欧美一区二区三区四区五区视频 | 樱桃视频一区二区 | 国产精品嫩草久久久久yw193 | 18 无码 高潮 蜜臀 | 日韩手机在线视频 |