目录
01.数组扁平化
方法一:使用flat()
方法二:利用正则
方法三:正则改良版本
方法四:使用reduce
方法五:函数递归
02.数组去重
方法一:利用Set
方法二:两层for循环+splice
方法三:利用indexOf
方法四:利用include
方法五:利用filter
方法六:利用Map
03.类数组转化为数组
方法一:Array.from
方法二:Array.prototype.slice.call()
方法三:扩展运算符
方法四:利用concat
04.Array.prototype.filter()
05.Array.prototype.map()
06.Array.prototype.forEach()
07.Array.prototype.reduce()
08.Function.prototype.apply()
09.Function.prototype.call
10.Function.prototype.bind
11.debounce(防抖)
12.throttle(节流)
13.函数珂里化
14.模拟new操作
15.instanceof
16.原型继承
17.Object.is
18.Object.assign
19.深拷贝
20.Promise
21.Promise.all
22.Promise.race
23.Promise并行限制
24.JSONP
25.AJAX
26.event模块
27.图片懒加载
28.滚动加载
29.渲染几万条数据不卡住页面
30.打印出当前网页使用了多少种HTML元素
31.将VirtualDom转化为真实DOM结构
32.字符串解析问题
首页 web前端 js教程 32个纯手写JS,巩固你的JS基础

32个纯手写JS,巩固你的JS基础

Oct 07, 2020 pm 03:55 PM
js基础

javascript栏目为大家介绍32个纯手写JS,巩固JS基础(面试高频),一起学习吧。

32个纯手写JS,巩固你的JS基础

作为前端开发,JS是重中之重,最近结束了面试的高峰期,基本上offer也定下来了就等开奖,趁着这个时间总结下32个手写JS问题,这些都是高频面试题,希望对你能有所帮助。

关于源码都紧遵规范,都可跑通MDN示例,其余的大多会涉及一些关于JS的应用题和本人面试过程

01.数组扁平化

数组扁平化是指将一个多维数组变为一个一维数组

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(',');复制代码
登录后复制

但数据类型都会变为字符串

方法三:正则改良版本

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);复制代码
登录后复制

方法五:函数递归

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;, {}, {}]复制代码
登录后复制

方法一:利用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;
}复制代码
登录后复制

03.类数组转化为数组

类数组是具有length属性,但不具有数组原型上的方法。常见的类数组有arguments、DOM操作方法返回的结果。

方法一:Array.from

Array.from(document.querySelectorAll(&#39;p&#39;))复制代码
登录后复制

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

Array.prototype.slice.call(document.querySelectorAll(&#39;p&#39;))复制代码
登录后复制

方法三:扩展运算符

[...document.querySelectorAll(&#39;p&#39;)]复制代码
登录后复制

方法四:利用concat

Array.prototype.concat.apply([], document.querySelectorAll(&#39;p&#39;));复制代码
登录后复制

04.Array.prototype.filter()

32个纯手写JS,巩固你的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;
}复制代码
登录后复制

对于>>>0有疑问的:解释>>>0的作用

05.Array.prototype.map()

32个纯手写JS,巩固你的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()

32个纯手写JS,巩固你的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()

32个纯手写JS,巩固你的JS基础
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()

第一个参数是绑定的this,默认为window,第二个参数是数组或类数组

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

call唯一不同的是,call()方法接受的是一个参数列表

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(防抖)

触发高频时间后n秒内函数只会执行一次,如果n秒内高频时间再次触发,则重新计算时间。

const debounce = (fn, time) => {  let timeout = null;  return function() {
    clearTimeout(timeout)
    timeout = setTimeout(() => {
      fn.apply(this, arguments);
    }, time);
  }
};复制代码
登录后复制

防抖常应用于用户进行搜索输入节约请求资源,window触发resize事件时进行防抖只触发一次。

12.throttle(节流)

高频时间触发,但n秒内只会执行一次,所以节流会稀释函数的执行频率。

const throttle = (fn, time) => {  let flag = true;  return function() {    if (!flag) return;
    flag = false;
    setTimeout(() => {
      fn.apply(this, arguments);
      flag = true;
    }, time);
  }
}复制代码
登录后复制

节流常应用于鼠标不断点击触发、监听滚动事件。

13.函数珂里化

指的是将一个接受多个参数的函数 变为 接受一个参数返回一个函数的固定形式,这样便于再次调用,例如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.模拟new操作

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(视频)

以上是32个纯手写JS,巩固你的JS基础的详细内容。更多信息请关注PHP中文网其他相关文章!

本站声明
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

热AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover

AI Clothes Remover

用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool

Undress AI Tool

免费脱衣服图片

Clothoff.io

Clothoff.io

AI脱衣机

Video Face Swap

Video Face Swap

使用我们完全免费的人工智能换脸工具轻松在任何视频中换脸!

热工具

记事本++7.3.1

记事本++7.3.1

好用且免费的代码编辑器

SublimeText3汉化版

SublimeText3汉化版

中文版,非常好用

禅工作室 13.0.1

禅工作室 13.0.1

功能强大的PHP集成开发环境

Dreamweaver CS6

Dreamweaver CS6

视觉化网页开发工具

SublimeText3 Mac版

SublimeText3 Mac版

神级代码编辑软件(SublimeText3)

热门话题

Java教程
1659
14
CakePHP 教程
1415
52
Laravel 教程
1309
25
PHP教程
1257
29
C# 教程
1231
24
神秘的JavaScript:它的作用以及为什么重要 神秘的JavaScript:它的作用以及为什么重要 Apr 09, 2025 am 12:07 AM

JavaScript是现代Web开发的基石,它的主要功能包括事件驱动编程、动态内容生成和异步编程。1)事件驱动编程允许网页根据用户操作动态变化。2)动态内容生成使得页面内容可以根据条件调整。3)异步编程确保用户界面不被阻塞。JavaScript广泛应用于网页交互、单页面应用和服务器端开发,极大地提升了用户体验和跨平台开发的灵活性。

JavaScript的演变:当前的趋势和未来前景 JavaScript的演变:当前的趋势和未来前景 Apr 10, 2025 am 09:33 AM

JavaScript的最新趋势包括TypeScript的崛起、现代框架和库的流行以及WebAssembly的应用。未来前景涵盖更强大的类型系统、服务器端JavaScript的发展、人工智能和机器学习的扩展以及物联网和边缘计算的潜力。

JavaScript引擎:比较实施 JavaScript引擎:比较实施 Apr 13, 2025 am 12:05 AM

不同JavaScript引擎在解析和执行JavaScript代码时,效果会有所不同,因为每个引擎的实现原理和优化策略各有差异。1.词法分析:将源码转换为词法单元。2.语法分析:生成抽象语法树。3.优化和编译:通过JIT编译器生成机器码。4.执行:运行机器码。V8引擎通过即时编译和隐藏类优化,SpiderMonkey使用类型推断系统,导致在相同代码上的性能表现不同。

JavaScript:探索网络语言的多功能性 JavaScript:探索网络语言的多功能性 Apr 11, 2025 am 12:01 AM

JavaScript是现代Web开发的核心语言,因其多样性和灵活性而广泛应用。1)前端开发:通过DOM操作和现代框架(如React、Vue.js、Angular)构建动态网页和单页面应用。2)服务器端开发:Node.js利用非阻塞I/O模型处理高并发和实时应用。3)移动和桌面应用开发:通过ReactNative和Electron实现跨平台开发,提高开发效率。

Python vs. JavaScript:学习曲线和易用性 Python vs. JavaScript:学习曲线和易用性 Apr 16, 2025 am 12:12 AM

Python更适合初学者,学习曲线平缓,语法简洁;JavaScript适合前端开发,学习曲线较陡,语法灵活。1.Python语法直观,适用于数据科学和后端开发。2.JavaScript灵活,广泛用于前端和服务器端编程。

如何使用Next.js(前端集成)构建多租户SaaS应用程序 如何使用Next.js(前端集成)构建多租户SaaS应用程序 Apr 11, 2025 am 08:22 AM

本文展示了与许可证确保的后端的前端集成,并使用Next.js构建功能性Edtech SaaS应用程序。 前端获取用户权限以控制UI的可见性并确保API要求遵守角色库

从C/C到JavaScript:所有工作方式 从C/C到JavaScript:所有工作方式 Apr 14, 2025 am 12:05 AM

从C/C 转向JavaScript需要适应动态类型、垃圾回收和异步编程等特点。1)C/C 是静态类型语言,需手动管理内存,而JavaScript是动态类型,垃圾回收自动处理。2)C/C 需编译成机器码,JavaScript则为解释型语言。3)JavaScript引入闭包、原型链和Promise等概念,增强了灵活性和异步编程能力。

使用Next.js(后端集成)构建多租户SaaS应用程序 使用Next.js(后端集成)构建多租户SaaS应用程序 Apr 11, 2025 am 08:23 AM

我使用您的日常技术工具构建了功能性的多租户SaaS应用程序(一个Edtech应用程序),您可以做同样的事情。 首先,什么是多租户SaaS应用程序? 多租户SaaS应用程序可让您从唱歌中为多个客户提供服务

See all articles