前端原生JS代码按需加载

问题描述

最近公司需要对产品的性能进行提升,但是,因为产品的结构是原生es5代码写出来的,这就存在一下几个问题

1.首页加载缓慢

因为是单页面的app,并且产品的结构比较庞大,一个页面引入了300多个js文件以及10几个css文件,这就导致了产品的首页加载非常缓慢,具体是有多慢呢?大概是7s左右。这肯定是不符合现代的这种前端的趋势的。非常影响体验。

2.请求并发数多

文件的加载量太过于庞大,对于高并发首页面加载的情况下,后台服务的确面临着很大的压力。

解决思路

首先,对于第一个问题,我们的产品是一个单页面,未使用框架开发的产品,所以,按需加载的模式无法使用现在前端非常流行的框架进行改造,如果这样的话,投入的人力会较大,不如直接开发一个新的产品。所以,针对这种情况,能想到的是通过其他的思路,进行优化。
在前端发展至框架化之前,按需加载似乎能想到的是requireJs等那一系列的按需架子框架。,但是问题是,需要原生的js代码支持这种加载方式,还需要重新梳理各个文件及各个类之间的关系,这样也是一个很费劲的事情。这种想法也是被排除掉了
所以问题就来了,但是还有好的一方面是,我们的产品是面向对象的方式进行各个模块之间的逻辑连接的。所以,我想到的是,只需要在创建类的对象时,做一些文章。

1.单个类的按需加载

加入现在有一个类,名称是classA, 在产品中的文件名是a.js,

function classA (){

}
classA.prototype.a = function(){
 
}

我们在new classA时,如果没有classA,当然会报错,但是如果我们重现改写一下现在classA的构造函数

function classA (){
  var xhr = new XMLHttpRequest;
  xhr.open('GET', './a.js', false)
  if(xhr.readyState === 4 && xhr.status === 200) {
    var script = document.createElement('script');
    script.text = "//@sourceURL=" + './a.js' + '\n' + xhr.response;
    document.head.appendChild(script)
  }
  return new classA
}

这样,就可以保证我需要加载classA的时候,如过classA是我们需要的classA,会走构造函数1,但是如果不是的话,首先会重新请求‘./a.js’,然后classA构造函数会被重构,然后重新创建classA的对象,返回即可。这样就保证了classA的按需加载。
但是,有问题的是,在项目具体实践中,有些类是用es6语法写的。例如现在,真实的classA使用这种方式声明

class classA{
  
}

你会发现,继续使用这种方法,是会报错的;这是因为在es6语法中,不允许class声明类时,类的名称重复。
所以,我们需要对classA做如下改造

class classA_1{

}
var classA = classA_1

这样,就不会发生类名重复的错误了。就可以用之前的方案进行项目改造。

2.类的继承

上述思路解决了单个类的按需加载,然而问题是大多数情况下,类之间是有继承关系的。
那么我们看一下es5的类继承如何进行的,这里就不说具体的逻辑,只列出代码

function extend(Sub, Super) {
  Sub.prototype = new Super();
  //改成:
  Sub.prototype = Object.create(Super.prototype);
}

我们可以看到,子类Sub在继承基类Super时,会先创建基类的对象,这其实,就和我们之前的思路,保持一致。只需要重写基类的构造函数,即可。
然而对于es6,继承的逻辑是

class classSub extends classSuper{

}

此时,继承的逻辑我们不能自定义,所以,这就需要子类在继承基类之前,保证基类的构造函数存在并且是我们所要的。
所以,我们需要这样重构classSub的构造函数

function classSub (){
  new classSuper
  var xhr = new XMLHttpRequest;
  xhr.open('GET', './subclass.js', false)
  if(xhr.readyState === 4 && xhr.status === 200) {
    var script = document.createElement('script');
    script.text = "//@sourceURL=" + './subclass.js' + '\n' + xhr.response;
    document.head.appendChild(script)
  }
  return new classSub
}

这样,就可以保证classSuper是在classSub构造函数重写时存在并且是我们所要的构造函数。

3.解决切换时卡顿问题

根据以上的方案,的确解决了按需加载js文件的问题,我们的产品,首页面加载速度也从之前的7s左右优化到了800ms左右,之前的300多个请求,也被降到了20多个,这也达成了预想。但是,这也造成了问题,在切换到具体的应用组件页面时,因为会多出很多同步的请求,这会造成卡顿,比如,我们产品实际遇到过,一个页面应用比较复杂,这需要加载170多个js文件,还是同步的请求,这就造成了3-4s多之后,具体的页面才出现,这不是我们所希望看到的。
所以,我们需要进一步对js文件的加载进行改造。最先想到的是,利用闲置时间,加载剩余js文件。按照这个思路,我最先想到的是 webWorker,webWorker是能够开启浏览器多线程的一个法宝,虽然有很多限制,但是用于发送请求,加载js文件的话,确实是一个不错的选择。
所以,我这里建立一个workerload.js的文件,里面写的逻辑就是加载读取js文件的数据,并且将数据返给主线程,这里值得注意的是,需要返回给主线程一个具体的类名,用来判断主线程是否加载过这个类。所以,最后的成品是这样的,在主线程中,我们需要重现定义各个类的构造函数。如

function classLoading(url){
  var xhr = new XMLHttpRequest;
  xhr.open('GET', url, false)
  if(xhr.readyState === 4 && xhr.status === 200) {
    var script = document.createElement('script');
    script.text = "//@sourceURL=" + url + '\n' + xhr.response;
    document.head.appendChild(script)
  }
}
function classSub (...data){
  new classSuper
  var url = './classsub.js'
  classLoading(url)
classSub.alreadyLoaded = true; //表示该类已加载
  return new classSub(...data)
}

其中用到class的静态属性,将这个类标记为已加载的类,防止重复加载。
之后,建立workerload.js,在子线程中利用空闲时间加载js文件;

function classLoading(file){
  var xhr = new XMLHttpRequest;
  xhr.open('GET', file.url, true);
  xhr.send(null)
  if(xhr.readyState === 4 && xhr.status === 200) {
    var result = {
      name:file.name,
      content:xhr.response,
      url:file.url
    }
    postMessage(result)
  }
}
var files = [
  {name:'classSub',url:'./classsub.js'},  ...
]
files.forEach(file=>{
  classLoading(file)
})

可以看到的是,在webworker加载js时,可以使用异步方案加载js文件,这样会大大提高加载js文件的性能。但是有问题的是,如果加载的js文件有前后依赖关系,需要先加载优先级较高的,这需要用到同步加载。我在项目改造的时候就是用的这种方案。
有了workerload.js之后,就可以在主线程中进行空闲时间加载js文件了

window.loadFIleWorker = new Worker('./workerload.js')
loadFIleWorker.onmessage = event =>{
  if(window[event.data.name] && !window[event.data.name].alreadyLoaded) {
	window[event.data.name].alreadyLoaded = true; //用来标记该类已加载
    var script = document.createElement('script');
    script.text = "//@sourceURL=" + event.data.url + '\n' + event.data.content;
    document.head.appendChild(script)
  }
}

这里具体就是使用webworker开启多线程,在worker线程中用来加载js,并且监听worker线程发送过来的数据,用来动态的将js脚本写入到页面中
值得注意的是,我在项目中遇到的问题是,一次性用worker线程加载大量的js,导致主线程会持续接收worker线程发来的数据,这就会造成一段时间的卡顿,为了解决这个问题,我令worker线程分批次加载js文件,一次40个左右,可以挑选合适的时机去加载js文件

4.文件的合并

为了解决请求并发数多的问题,我是针对文件进行合并,减少并发量,这样可以解决服务器并发带来的压力。这里可以根据自己的具体项目进行

总结

本文是解决使用无框架化开发的项目按需加载的问题,可以解决首页面加载慢的问题,以及各个应用切换缓慢的问题。
主要的方案总结如下

  1. 重写构造函数,(如果继承方案和上述不一致,没有new基类,也需要改造)
  2. 利用webWorker进行空闲时间的js文件加载。
  3. 文件合并,减少请求数
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值