搜索
首页web前端js教程32个纯手写JS,巩固你的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 !== '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()方法接受的是一个参数列表

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='default.png',当检测到图片出现在窗口之后再补充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中文网其他相关文章!

声明
本文转载于:juejin。如有侵权,请联系admin@php.cn删除
JavaScript的演变:当前的趋势和未来前景JavaScript的演变:当前的趋势和未来前景Apr 10, 2025 am 09:33 AM

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

神秘的JavaScript:它的作用以及为什么重要神秘的JavaScript:它的作用以及为什么重要Apr 09, 2025 am 12:07 AM

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

Python还是JavaScript更好?Python还是JavaScript更好?Apr 06, 2025 am 12:14 AM

Python更适合数据科学和机器学习,JavaScript更适合前端和全栈开发。 1.Python以简洁语法和丰富库生态着称,适用于数据分析和Web开发。 2.JavaScript是前端开发核心,Node.js支持服务器端编程,适用于全栈开发。

如何安装JavaScript?如何安装JavaScript?Apr 05, 2025 am 12:16 AM

JavaScript不需要安装,因为它已内置于现代浏览器中。你只需文本编辑器和浏览器即可开始使用。1)在浏览器环境中,通过标签嵌入HTML文件中运行。2)在Node.js环境中,下载并安装Node.js后,通过命令行运行JavaScript文件。

在Quartz中如何在任务开始前发送通知?在Quartz中如何在任务开始前发送通知?Apr 04, 2025 pm 09:24 PM

如何在Quartz中提前发送任务通知在使用Quartz定时器进行任务调度时,任务的执行时间是由cron表达式设定的。现�...

在JavaScript中,如何在构造函数中获取原型链上函数的参数?在JavaScript中,如何在构造函数中获取原型链上函数的参数?Apr 04, 2025 pm 09:21 PM

在JavaScript中如何获取原型链上函数的参数在JavaScript编程中,理解和操作原型链上的函数参数是常见且重要的任�...

微信小程序webview中Vue.js动态style位移失效是什么原因?微信小程序webview中Vue.js动态style位移失效是什么原因?Apr 04, 2025 pm 09:18 PM

在微信小程序web-view中使用Vue.js动态style位移失效的原因分析在使用Vue.js...

在Tampermonkey中如何实现对多个链接的并发GET请求并依次判断返回结果?在Tampermonkey中如何实现对多个链接的并发GET请求并依次判断返回结果?Apr 04, 2025 pm 09:15 PM

在Tampermonkey中如何对多个链接进行并发GET请求并依次判断返回结果?在Tampermonkey脚本中,我们经常需要对多个链...

See all articles

热AI工具

Undresser.AI Undress

Undresser.AI Undress

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

AI Clothes Remover

AI Clothes Remover

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

Undress AI Tool

Undress AI Tool

免费脱衣服图片

Clothoff.io

Clothoff.io

AI脱衣机

AI Hentai Generator

AI Hentai Generator

免费生成ai无尽的。

热门文章

R.E.P.O.能量晶体解释及其做什么(黄色晶体)
3 周前By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.最佳图形设置
3 周前By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.如果您听不到任何人,如何修复音频
3 周前By尊渡假赌尊渡假赌尊渡假赌
WWE 2K25:如何解锁Myrise中的所有内容
3 周前By尊渡假赌尊渡假赌尊渡假赌

热工具

安全考试浏览器

安全考试浏览器

Safe Exam Browser是一个安全的浏览器环境,用于安全地进行在线考试。该软件将任何计算机变成一个安全的工作站。它控制对任何实用工具的访问,并防止学生使用未经授权的资源。

SublimeText3 Mac版

SublimeText3 Mac版

神级代码编辑软件(SublimeText3)

Atom编辑器mac版下载

Atom编辑器mac版下载

最流行的的开源编辑器

SublimeText3 英文版

SublimeText3 英文版

推荐:为Win版本,支持代码提示!

记事本++7.3.1

记事本++7.3.1

好用且免费的代码编辑器