ホームページ > ウェブフロントエンド > jsチュートリアル > JS の基礎を強化する 32 個の純粋な手書き JS

JS の基礎を強化する 32 個の純粋な手書き JS

coldplay.xixi
リリース: 2020-10-07 15:55:59
転載
2843 人が閲覧しました

javascript コラムでは、JS の基礎を固めるための 32 個の純手書き JS (高頻度インタビュー) を紹介します。一緒に学びましょう。

JS の基礎を強化する 32 個の純粋な手書き JS

フロントエンド開発としてはJSが最優先です。最近面接の繁忙期も終わりました。基本的には内定が確定しており、ご連絡をお待ちしております。この時間を利用してまとめてみます 以下は面接でよく聞かれる手書きのJSの質問32個です。

ソース コードは仕様に従っており、MDN サンプルを通じて実行できます。残りのほとんどには、JS に関するいくつかのアプリケーションの質問と私の面接プロセスが含まれます

01. 配列の平坦化

配列のフラット化とは、多次元配列を 1 次元配列に変換することを指します

const arr = [1, [2, [3, [4, 5]]], 6];// => [1, 2, 3, 4, 5, 6]复制代码
ログイン後にコピー

方法 1: flat() を使用します

const res1 = arr.flat(Infinity);复制代码
ログイン後にコピー

方法 2: 正規表現を使用します

const res2 = JSON.stringify(arr).replace(/\[|\]/g, '').split(',');复制代码
ログイン後にコピー

ただし、データ型は string に変更されます

方法 3: 通常の改良版

const res3 = JSON.parse('[' + JSON.stringify(arr).replace(/\[|\]/g, '') + ']');复制代码
ログイン後にコピー

方法 4: Reduce を使用します

const flatten = arr => {  return arr.reduce((pre, cur) => {    return pre.concat(Array.isArray(cur) ? flatten(cur) : cur);
  }, [])
}const res4 = flatten(arr);复制代码
ログイン後にコピー

方法 5: 関数再帰

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. 配列の重複排除

const arr = [1, 1, &#39;1&#39;, 17, true, true, false, false, &#39;true&#39;, &#39;a&#39;, {}, {}];// => [1, &#39;1&#39;, 17, true, false, &#39;true&#39;, &#39;a&#39;, {}, {}]复制代码
ログイン後にコピー

方法 1: Set を使用する

const res1 = Array.from(new Set(arr));复制代码
ログイン後にコピー

方法 2: 2 層の for ループ スプライス

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;
}复制代码
ログイン後にコピー

方法 3: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 を使用することもできます。考え方は似ています。

方法 4: 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;
}复制代码
ログイン後にコピー

方法 5: フィルターを使用します

const unique4 = arr => {  return arr.filter((item, index) => {    return arr.indexOf(item) === index;
  });
}复制代码
ログイン後にコピー

方法 6: 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. クラス配列を配列に変換します

クラス配列には length プロパティがありますが、配列プロトタイプのメソッドはありません。一般的なクラス配列には、arguments と DOM 操作メソッドによって返される結果が含まれます。

方法 1: Array.from

Array.from(document.querySelectorAll(&#39;p&#39;))复制代码
ログイン後にコピー

方法 2: Array.prototype.slice.call()

Array.prototype.slice.call(document.querySelectorAll(&#39;p&#39;))复制代码
ログイン後にコピー

方法 3: 拡張演算子

[...document.querySelectorAll(&#39;p&#39;)]复制代码
ログイン後にコピー

方法 4: concat

Array.prototype.concat.apply([], document.querySelectorAll(&#39;p&#39;));复制代码
ログイン後にコピー

04.Array.prototype.filter()

JS の基礎を強化する 32 個の純粋な手書き JS
##
Array.prototype.filter = function(callback, thisArg) {  if (this == undefined) {    throw new TypeError(&#39;this is null or not undefined&#39;);
  }  if (typeof callback !== &#39;function&#39;) {    throw new TypeError(callback + &#39;is not a function&#39;);
  }  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;
}复制代码
ログイン後にコピー
For

> > を使用する>0 質問: 説明>>>0

05.Array.prototype.map()

JS の基礎を強化する 32 個の純粋な手書き JS
## の役割
Array.prototype.map = function(callback, thisArg) {  if (this == undefined) {    throw new TypeError(&#39;this is null or not defined&#39;);
  }  if (typeof callback !== &#39;function&#39;) {    throw new TypeError(callback + &#39; is not a function&#39;);
  }  const res = [];  // 同理
  const O = Object(this);  const len = O.length >>> 0;  for (let i = 0; i < len; i++) {    if (i in O) {      // 调用回调函数并传入新数组
      res[i] = callback.call(thisArg, O[i], i, this);
    }
  }  return res;
}复制代码
ログイン後にコピー
06.Array.prototype.forEach()

JS の基礎を強化する 32 個の純粋な手書き JS
##forEach
は、map と似ていますが、唯一の違いは次のとおりです。

forEach には戻り値がありません。

Array.prototype.forEach = function(callback, thisArg) {  if (this == null) {    throw new TypeError(&#39;this is null or not defined&#39;);
  }  if (typeof callback !== "function") {    throw new TypeError(callback + &#39; is not a function&#39;);
  }  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(&#39;this is null or not defined&#39;);
  }  if (typeof callback !== &#39;function&#39;) {    throw new TypeError(callbackfn + &#39; is not a function&#39;);
  }  const O = Object(this);  const len = this.length >>> 0;  let accumulator = initialValue;  let k = 0;  // 如果第二个参数为undefined的情况下
  // 则数组的第一个有效值作为累加器的初始值
  if (accumulator === undefined) {    while (k < len && !(k in O)) {
      k++;
    }    // 如果超出数组界限还没有找到累加器的初始值,则TypeError
    if (k >= len) {      throw new TypeError(&#39;Reduce of empty array with no initial value&#39;);
    }
    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()JS の基礎を強化する 32 個の純粋な手書き JS
最初の 1 つパラメータはこれにバインドされています。デフォルトは

window

です。2 番目のパラメータは配列または配列のような

Function.prototype.apply = function(context = window, args) {  if (typeof this !== &#39;function&#39;) {    throw new TypeError(&#39;Type Error&#39;);
  }  const fn = Symbol(&#39;fn&#39;);
  context[fn] = this;  const res = context[fn](...args);  delete context[fn];  return res;
}复制代码
ログイン後にコピー
09.Function.prototype.call

yu

です。 call

唯一の違いは、

call() メソッドがパラメーター list

Function.prototype.call = function(context = window, ...args) {  if (typeof this !== &#39;function&#39;) {    throw new TypeError(&#39;Type Error&#39;);
  }  const fn = Symbol(&#39;fn&#39;);
  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 !== &#39;function&#39;) {    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( Anti -shake)

この関数は、高周波時間がトリガーされてから n 秒以内に 1 回だけ実行されます。n 秒以内に再度高周波時間がトリガーされると、時間が再計算されます。

const debounce = (fn, time) => {  let timeout = null;  return function() {
    clearTimeout(timeout)
    timeout = setTimeout(() => {
      fn.apply(this, arguments);
    }, time);
  }
};复制代码
ログイン後にコピー

アンチシェイクは、ユーザーがリクエスト リソースを保存するために検索入力を実行するときによく使用されます。アンチシェイクは、

window

resize イベントをトリガーするときに 1 回だけトリガーされます。 12.throttle (スロットル)

高頻度の時間トリガーですが、n 秒に 1 回しか実行されないため、スロットリングにより関数の実行頻度が減ります。

const throttle = (fn, time) => {  let flag = true;  return function() {    if (!flag) return;
    flag = false;
    setTimeout(() => {
      fn.apply(this, arguments);
      flag = true;
    }, time);
  }
}复制代码
ログイン後にコピー

スロットリングは、連続的なマウス クリックをトリガーしたり、スクロール イベントを監視したりするためによく使用されます。

13. 関数の明確化

は、複数のパラメータを受け入れる関数を、1 つのパラメータを受け入れて関数を返す固定形式に変更して、再度呼び出せるようにすることを指します。例 f(1)(2)

古典的な面接の質問:

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. 新しい操作のシミュレーション

3 ステップ:

  1. ctor.prototype为原型创建一个对象。
  2. 执行构造函数并将this绑定到新创建的对象上。
  3. 判断构造函数执行返回的结果是否是引用数据类型,若是则返回构造函数执行的结果,否则返回创建的对象。
function newOperator(ctor, ...args) {  if (typeof ctor !== &#39;function&#39;) {    throw new TypeError(&#39;Type Error&#39;);
  }  const obj = Object.create(ctor.prototype);  const res = ctor.apply(obj, args);  const isObject = typeof res === &#39;object&#39; && res !== null;  const isFunction = typeof res === &#39;function&#39;;  return isObject || isFunction ? res : obj;
}复制代码
ログイン後にコピー

15.instanceof

instanceof运算符用于检测构造函数的prototype属性是否出现在某个实例对象的原型链上。

const myInstanceof = (left, right) => {  // 基本数据类型都返回false
  if (typeof left !== &#39;object&#39; || 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 = &#39;parent&#39;;
}function Child() {
  Parent.call(this);  this.type = &#39;children&#39;;
}
Child.prototype = Object.create(Parent.prototype);
Child.prototype.constructor = Child;复制代码
ログイン後にコピー

17.Object.is

Object.is解决的主要是这两个问题:

+0 === -0  // true
NaN === NaN // false复制代码
ログイン後にコピー
const is= (x, y) => {  if (x === y) {    // +0和-0应该不相等
    return x !== 0 || y !== 0 || 1/x === 1/y;
  } else {    return x !== x && y !== y;
  }
}复制代码
ログイン後にコピー

18.Object.assign

Object.assign()方法用于将所有可枚举属性的值从一个或多个源对象复制到目标对象。它将返回目标对象(请注意这个操作是浅拷贝)

Object.defineProperty(Object, &#39;assign&#39;, {  value: function(target, ...args) {    if (target == null) {      return new TypeError(&#39;Cannot convert undefined or null to object&#39;);
    }    
    // 目标对象需要统一是引用数据类型,若不是会自动转换
    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()) => {  // 对于传入参数处理
  if (typeof target !== &#39;object&#39; || 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] === &#39;object&#39; && 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] === &#39;object&#39; && target[i] !== null
        ? cloneDeep1(target[i], hash)
        : target[i];
    }
  }  return cloneTarget;
}复制代码
ログイン後にコピー

20.Promise

实现思路:Promise源码实现

const PENDING = &#39;PENDING&#39;;      // 进行中const FULFILLED = &#39;FULFILLED&#39;;  // 已成功const REJECTED = &#39;REJECTED&#39;;    // 已失败class Promise {  constructor(exector) {    // 初始化状态
    this.status = PENDING;    // 将成功、失败结果放在this上,便于then、catch访问
    this.value = undefined;    this.reason = undefined;    // 成功态回调函数队列
    this.onFulfilledCallbacks = [];    // 失败态回调函数队列
    this.onRejectedCallbacks = [];    const resolve = value => {      // 只有进行中状态才能更改状态
      if (this.status === PENDING) {        this.status = FULFILLED;        this.value = value;        // 成功态函数依次执行
        this.onFulfilledCallbacks.forEach(fn => fn(this.value));
      }
    }    const reject = reason => {      // 只有进行中状态才能更改状态
      if (this.status === PENDING) {        this.status = REJECTED;        this.reason = reason;        // 失败态函数依次执行
        this.onRejectedCallbacks.forEach(fn => fn(this.reason))
      }
    }    try {      // 立即执行executor
      // 把内部的resolve和reject传入executor,用户可调用resolve和reject
      exector(resolve, reject);
    } catch(e) {      // executor执行出错,将错误内容reject抛出去
      reject(e);
    }
  }
  then(onFulfilled, onRejected) {
    onFulfilled = typeof onFulfilled === &#39;function&#39; ? onFulfilled : value => value;
    onRejected = typeof onRejected === &#39;function&#39;? 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 {            // 模拟微任务
            setTimeout(() => {              const result = onFulfilled(self.value);              // 分两种情况:
              // 1. 回调函数返回值是Promise,执行then操作
              // 2. 如果不是Promise,调用新Promise的resolve函数
              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) : reject(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对象,状态为FULFILLED
      return new Promise((resolve, reject) => resolve(value));
    }
  }  static reject(reason) {    return new Promise((resolve, reject) => {
      reject(reason);
    })
  }
}复制代码
ログイン後にコピー

21.Promise.all

Promise.all是支持链式调用的,本质上就是返回了一个Promise实例,通过resolvereject来改变实例状态。

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实例需要转化为Promise实例
      Promise.resolve(p).then(        val => resolve(val),
        err => reject(err),
      )
    })
  })
}复制代码
ログイン後にコピー

23.Promise并行限制

就是实现有并行限制的Promise调度器问题。

详细实现思路:某条高频面试原题:实现有并行限制的Promise调度器

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, &#39;1&#39;);
addTask(500, &#39;2&#39;);
addTask(300, &#39;3&#39;);
addTask(400, &#39;4&#39;);
scheduler.taskStart()// 2// 3// 1// 4复制代码
ログイン後にコピー

24.JSONP

script标签不遵循同源协议,可以用来进行跨域请求,优点就是兼容性好但仅限于GET请求

const jsonp = ({ url, params, callbackName }) => {  const generateUrl = () => {    let dataSrc = &#39;&#39;;    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(&#39;script&#39;);
    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(&#39;Mscrosoft.XMLHttp&#39;);
    xhr.open(&#39;GET&#39;, url, false);
    xhr.setRequestHeader(&#39;Accept&#39;, &#39;application/json&#39;);
    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模块

实现node中回调函数的机制,node中回调函数其实是内部使用了观察者模式

观察者模式:定义了对象间一种一对多的依赖关系,当目标对象Subject发生改变时,所有依赖它的对象Observer都会得到通知。

function EventEmitter() {  this.events = new Map();
}// 需要实现的一些方法:// addListener、removeListener、once、removeAllListeners、emit// 模拟实现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 === &#39;function&#39;) {    // 目前type事件只有一个回调
    this.events.set(type, [hanlder, wrapCallback(fn, once)]);
  } else {    // 目前type事件数>=2
    hanlder.push(wrapCallback(fn, once));
  }
}// 模拟实现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]);
      }
    }
  }
}// 模拟实现once方法EventEmitter.prototype.once = function(type, listener) {  this.addListener(type, listener, true);
}// 模拟实现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标签统一自定义属性src=&#39;default.png&#39;,当检测到图片出现在窗口之后再补充src属性,此时才会进行图片资源加载。

function lazyload() {  const imgs = document.getElementsByTagName(&#39;img&#39;);  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;
    }
  }
}// 可以使用节流优化一下window.addEventListener(&#39;scroll&#39;, lazyload);复制代码
ログイン後にコピー

28.滚动加载

原理就是监听页面滚动事件,分析clientHeightscrollTopscrollHeight三者的属性关系。

window.addEventListener(&#39;scroll&#39;, function() {  const clientHeight = document.documentElement.clientHeight;  const scrollTop = document.documentElement.scrollTop;  const scrollHeight = document.documentElement.scrollHeight;  if (clientHeight + scrollTop >= scrollHeight) {    // 检测到滚动至页面底部,进行后续操作
    // ...
  }
}, false);复制代码
ログイン後にコピー

一个Demo:页面滚动加载的Demo

29.渲染几万条数据不卡住页面

渲染大数据时,合理使用createDocumentFragmentrequestAnimationFrame,将操作切分为一小段一小段执行。

setTimeout(() => {  // 插入十万条数据
  const total = 100000;  // 一次插入的数据
  const once = 20;  // 插入数据需要的次数
  const loopCount = Math.ceil(total / once);  let countOfRender = 0;  const ul = document.querySelector(&#39;ul&#39;);  // 添加数据的方法
  function add() {    const fragment = document.createDocumentFragment();    for(let i = 0; i < once; i++) {      const li = document.createElement(&#39;li&#39;);
      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.打印出当前网页使用了多少种HTML元素

一行代码可以解决:

const fn = () => {  return [...new Set([...document.querySelectorAll(&#39;*&#39;)].map(el => el.tagName))].length;
}复制代码
ログイン後にコピー

值得注意的是:DOM操作返回的是类数组,需要转换为数组之后才可以调用数组的方法。

31.将VirtualDom转化为真实DOM结构

这是当前SPA应用的核心概念之一

// vnode结构:// {//   tag,//   attrs,//   children,// }//Virtual DOM => DOMfunction render(vnode, container) {
  container.appendChild(_render(vnode));
}function _render(vnode) {  // 如果是数字类型转化为字符串
  if (typeof vnode === &#39;number&#39;) {
    vnode = String(vnode);
  }  // 字符串类型直接就是文本节点
  if (typeof vnode === &#39;string&#39;) {    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);
    })
  }  // 子数组进行递归操作
  vnode.children.forEach(child => render(child, dom));  return dom;
}复制代码
ログイン後にコピー

32.字符串解析问题

var a = {    b: 123,    c: &#39;456&#39;,    e: &#39;789&#39;,
}var str=`a{a.b}aa{a.c}aa {a.d}aaaa`;// => &#39;a123aa456aa {a.d}aaaa&#39;复制代码
ログイン後にコピー

实现函数使得将str字符串中的{}内的变量替换,如果属性不存在保持原样(比如{a.d}

类似于模版字符串,但有一点出入,实际上原理大差不差

const fn1 = (str, obj) => {    let res = &#39;&#39;;    // 标志位,标志前面是否有{
    let flag = false;    let start;    for (let i = 0; i < str.length; i++) {        if (str[i] === &#39;{&#39;) {
            flag = true;
            start = i + 1;            continue;
        }        if (!flag) res += str[i];        else {            if (str[i] === &#39;}&#39;) {
                flag = false;
                res += match(str.slice(start, i), obj);
            }
        }
    }    return res;
}// 对象匹配操作const match = (str, obj) => {    const keys = str.split(&#39;.&#39;).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;
}复制代码
ログイン後にコピー

相关免费学习推荐:javascript(视频)

以上がJS の基礎を強化する 32 個の純粋な手書き JSの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

関連ラベル:
ソース:juejin.im
このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
最新の問題
人気のチュートリアル
詳細>
最新のダウンロード
詳細>
ウェブエフェクト
公式サイト
サイト素材
フロントエンドテンプレート