文章目录
声明:禁止商业转载!本文受益于互联网资料和个人总结,旨在学习交流非商业盈利,不存在版权纠纷。 如有错误欢迎批评指正!
2021年08月05日 编辑于湖北·武汉
1.1 前端模块化的意义
- 便捷引用方式
- 解决全局变量的灾难
- 代码量剧增带来的代码管理和维护
// 闭包
moduleA = function () {
var a,b;
return {
add: function (c){
return a + b + c;
};
}
}()
这样function内部的变量就对全局隐藏了,达到了封装的目的,但是最外层模块名还是暴露在全局,要是模快越来越多,依然会存在模块名冲突的问题。
1.1.1 同步加载CommonJS
CommonJS定义的模块分为:模块引用(require) 、模块输出(exports)、 模块标识(module),这里exports是module.exports的一个引用。像Node.js、Sea.js属于CommonJS规范。
特点:
- 每个文件都可当做一个模块,不会污染全局作用域;
- 在服务器端:同步加载模块;
- 在浏览器端:模块需要提前编译打包处理;
基本语法:
/**
* 1.模块定义
*/
// main.js
module.exports.add = function (a, b){
// core js
return a + b ;
}
// 也可以在一个文件中引入模块并导出另一个模块
// add.js
module.exports = function(a, b){
// core js
return a + b ;
}
var add = require('./add.js'); // 引入其他模块
module.exports.increment = function () {
return add(val, 1);
}
/**
* 2.模块引用
*/
var math = require('math.js')
var math = require('math')
math.add(1, 1)
由于CommonJS是同步加载模块,主要服务于服务器端。对于服务器端来说,所有的模块都放在本地硬盘,等待模块时间就是硬盘读取文件时间。但是,对于浏览器而言,它需要从服务器加载模块,涉及到网速,代理等原因,一旦等待时间过长,浏览器处于“假死”状态。所以在浏览器端,不完全适合于CommonJS规范。
因此在浏览器端又出现了一个规范——AMD。
1.1.2 异步模块定义 Asynchronous Module Definition (AMD)
AMD采用异步方式加载模块,通过define来定义一个模块,通过require来引入模块。AMD是RequireJS在推广过程中对模块定义的规范化产出,主要服务于浏览器端。
最早的时候,所有Javascript代码都写在一个文件里面,只要加载这一个文件就够了。后来,代码越来越多,一个文件不够了,必须分成多个文件,依次加载。
<script src="1.js"></script>
<script src="2.js"></script>
<script src="3.js"></script>
这样的写法有很大的缺点。首先,加载的时候,浏览器会停止网页渲染,加载文件越多,网页失去响应的时间就会越长;其次,由于js文件之间存在依赖关系,因此必须严格保证加载顺序,依赖性最大的模块一定要放到最后加载,当依赖关系很复杂的时候,代码的编写和维护都会变得困难。
RequireJS的诞生,就是为了解决这两个问题:
- 实现js文件的异步加载,避免网页失去响应;
- 管理模块之间的依赖性,便于代码的编写和维护。
特点:
- 模块的加载不影响后面语句的执行;
- 所有依赖于这些模块的语句都写在一个回调函数中;
- 而加载内部是同步的(加载完毕后,这个回调函数才运行);
基本语法:
define([id], [dependencies], factory)
- id: 可选,字符串类型,定义模块标识,如果没有提供参数,默认为文件名
- dependencies: 可选,字符串数组,AMD 推崇依赖前置,即当前模块依赖的其他模块,模块依赖必须在真正执行具体的factory方法前解决
- factory: 必需,函数方法,初始化模块需要执行的函数或对象。如果为函数,它只被执行一次。如果是对象,此对象会作为模块的输出值。
/**
* 1.模块定义
*/
// 1.1 定义没有依赖的模块
// main.js
define (function(){
var add = function(a, b){
return a + b;
}
return {
add:add
}
})
// 1.2 定义有依赖的模块
define(['moduleA','moduleB','moduleC'],function(A, B, C){
function add(a, b){
A.toString();
B.write();
C.reading();
return a + b;
}
return {
add:add
}
})
/**
* 2.模块引用
*/
// index.js
require(['math','jquery'], function (math, $){ // ['math','jquery']加载依赖,function(math, $) 回调
$("divId").click(function(){
alert(math.add(1,1));
});
});
通常情况下不同功能的模块存放在不同路径下,在调用时要严格按照路径加载,此时可以通过require.config方法来配置各个模块的加载路径,在引用时只需要加载配置文件即可。
// main.js
require.config({
paths: {
"jquery": "lib/jquery.min.js", // 模块在其他目录
"math": "math.js" // 模块在同一目录
}
})
// index.html
<script src="js/require.js" defer async="true" data-main="js/main.js"></script>
1.1.3 通用模块定义 Common Module Definition(CMD)
CMD规范是Sea.js在国内推广过程中产生的,和AMD一样都以浏览器端模块化开发为目的。CMD规范中一个文件就是一个模块,CMD推崇依赖就近,延迟执行,可以把你的依赖写进代码的任意一行。模块定义及模块引用代码如下:
/**
* 1.模块定义
*/
// 1.1 定义没有依赖的模块
// main.js
define (function(require, exports, module){
var add = function(a, b){
return a + b;
}
exports.add // or exports = add or module.exports = add
})
// 1.2 定义有依赖的模块
define(function(require, exports, module){
// 引入依赖模块(同步)
var A = require('./moduleA')
var write = require('./moduleB').write
// 引入依赖模块(异步)
require.async('./moduleC', function(C){
C.reading();
})
function add(a, b){
A.toString();
write();
return a + b;
}
exports.add
})
// 1.3 模块追加定义
define(function(require, exports, module){
var $ = require('lib/jquery.min.js')
function class(List){
var that = this;
that.a = List[0];
that.b = List[1]
}
// 通过prototype 添加构造方法
class.prototype.add= function(){
var that = this;
$("divId").click(function(c){ // 变量c 暴露给外部js文件
return that.msg(that, c); // 调用私有方法
}
});
// 类似private关键字思想,只有当前js文件可调用
function msg(that, c) {
alert(that.a + that.b)
};
module.exports = class;
})
同样sea.js也可以通过seajs.config,来配置存放在不同路径下不同功能的模块,在引用时只需要use模块的别名就行。
// sea-config.js
seajs.config({
base: "js",
alias: {
// 别名配置(用变量表示文件,解决路径层级过深和实现路径映射)
"main":"main.js"
}
});
/**
* 2.模块引用
*/
// index.html
<script>
seajs.use(['main'], function (main) {
var init = new main();
init.add()
}
</script>
1.2 CMD与AMD的区别
AMD和CMD最大的区别是对依赖的执行时机处理不同,注意不是加载的时机或者方式不同。
- 依赖加载的时机或者方式都是:异步;
- 对于依赖的模块,AMD 是提前执行,CMD 是延迟执行。也就是说AMD是预加载,CMD是懒加载。不过 RequireJS 从 2.0 开始,也改成可以延迟执行(根据写法不同,处理方式不同)。
- CMD 推崇依赖就近,AMD 推崇依赖前置。
- 模块定义方式不同,具体回看1.1.2章-1.1.3章。
方案 | 优势 | 劣势 | 特点 |
---|---|---|---|
AMD | 速度快 | 会浪费资源 | 预先加载所有的依赖,直到使用的时候才执行 |
CMD | 只有真正需要才加载依赖 | 性能较差 | 直到使用的时候才定义依赖 |
1.3 懒加载与预加载
1.3.1 懒加载
懒加载就是依赖延迟执行。
当访问一个页面的时候,先把< img >元素或是其他元素的背景图片路径替换成一张大小为1*1px图片的路径(这样就只需请求一次,俗称占位图),只有当图片出现在浏览器的可视区域内时,才设置图片正真的路径,让图片显示出来。这就是图片懒加载。
当页面内容图片较多,且图片质量较高,如果一次性加载完毕,不仅服务器压力大,而且页面响应时间较长影响用户体验。这时候就体现了懒加载的价值。
原理:
- 页面中的img元素,如果没有src属性,浏览器就不会发出请求去下载图片,只有通过javascript设置了图片路径,浏览器才会发送请求。
- 懒加载的原理就是先在页面中把所有的图片统一使用一张占位图进行占位,把正真的路径存在元素的“data-xxx”属性里,要用的时候就取出来在赋值给src属性;
步骤:
- 首先,不要将图片地址放到src属性中,而是放到其它属性(data-xxx)中。
- 页面加载完成后,根据scrollTop判断图片是否在用户的视野内,如果在,则将data-xxx属性中的值取出存放到src属性中
- 在滚动事件中重复判断图片是否进入视野,如果进入,则将data-xxx属性中的值取出存放到src属性中。
示例:
示例代码转载于 前端性能优化成神之路–图片懒加载(lazyload image)
- 方法一:原生JavaScript
<!--1. index.html -->
<style>
.box {
margin: 0 auto;
margin-top: 300px;
width: 300px;
background-image: url(src/img/loading.gif);
background-repeat: no-repeat;
background-position: 85px 45px;
}
.lazy {
width: 300px;
height: 400px; /*需要一个占位符,这里是设置一个高度来占位*/
}
</style>
<div id="test">
<div class="box"><img class="lazy" lazyload="true" data-original="src/img/01.jpg"></div>
<div class="box"><img class="lazy" lazyload="true" data-original="src/img/02.jpg"></div>
<div class="box"><img class="lazy" lazyload="true" data-original="src/img/03.jpg"></div>
<div class="box"><img class="lazy" lazyload="true" data-original="src/img/04.jpg"></div>
<div class="box"><img class="lazy" lazyload="true" data-original="src/img/05.jpg"></div>
<div class="box"><img class="lazy" lazyload="true" data-original="src/img/06.jpg"></div>
<div class="box"><img class="lazy" lazyload="true" data-original="src/img/07.jpg"></div>
<div class="box"><img class="lazy" lazyload="true" data-original="src/img/08.jpg"></div>
<div class="box"><img class="lazy" lazyload="true" data-original="src/img/09.jpg"></div>
</div>
// 2.lazyload.js
let viewHeight = document.documentElement.clientHeight // 获取可视区域的高度
function lazyload () {
// 获取所有标记了lazyload的img标签
let eles = document.querySelectorAll('img[data-original][lazyload]')
// 遍历所有的img标签
Array.prototype.forEach.call(eles, function (item, index) {
let rect
if (item.dataset.original === '')
return
rect = item.getBoundingClientRect()
if (rect.bottom >= 0 && rect.top < viewHeight) {
!function () {
var img = new Image()
img.src = item.dataset.original
img.onload = function () {
item.src = img.src
}
item.removeAttribute('data-original')
item.removeAttribute('lazyload')
}
}
})
}
lazyload() //将首屏的图片加载,因为没有进行scroll,所以需要手动的调用一下
// scroll(页面滚动的时候)监听lazyload方法,获取所有标记了lazyload的img标签
document.addEventListener('scroll', lazyload)
- 方法二:使用jquery-lazyload.js,jQuery的插件用于懒加载,主要相关参数:
- threshold: 实现滚到距离其xx px时就加载。
- placeholder: 为某一图片路径。此图片用来占据将要加载的图片的位置,待图片加载时,占位图则会隐藏,比如放一些等待加载的图片来优化用户体验效果。不设置设个参数,使用scc背景图来实现也是可以的,如实例中就是使用背景图的方式替代这个参数。
- event: 触发定义的事件时,图片才开始加载(此处click代表点击图片才开始加载,还有mouseover,sporty,foobar(…))
- effects: 图片显示时的效果,默认是show。
- container: 值为某容器.lazyload 默认在拉动浏览器滚动条时生效,这个参数可以让你在拉动某DIV的滚动条时依次加载其中的图片。 *
- failure_limit: 一般用于当页面中图片不连续时使用,滚动页面的时候,LazyLoad会循环为加载的图片。在循环中检测图片是否在可视区域内,插件默认情况下在找到第一张不在可见区域的图片时停止循环。
- skip_invisible: 为了提升性能,插件默认忽略隐藏的图片;如果想要加载隐藏图片,设置skip_invisible为false;
<!-- 1. index.html中引入jQuery和jQuery-lazyload -->
<script type="text/javascript" src="js/jquery-3.2.1.min.js"></script>
<script type="text/javascript" src="js/jquery.lazyload.min.js"></script>
<!-- 2. 使用data-original代替src属性 -->
<img class="lazy" data-original="src/img/01.jpg" style="margin-top:500px" height="300px">
<!-- 3. 设置图片懒加载参数 -->
<script type="text/javascript">
$(function() {
$("img.lazy").lazyload({
threshold: 0, // 距离其xx px时就加载。
placeholder: "src/img/loading.gif" // 加载之前
effect: "fadeIn", // 图片出现效果
//event: "click", // 添加事件
failure_limit: 20 // 找到 20 个不在可见区域的图片时才停止搜索
});
})
</script>
- 方式三:使用echo.js,专门用于实现懒加载,echo只有两个可选参数:
- offset: 离可视区域多少像素的图片可以被加载
- throttle: 图片延时多少毫秒加载
<!-- 1. index.html中引入echo.min.js -->
<script type="text/javascript" src="js/echo.min.js"></script>
<!-- 2. 使用data-echo代替src属性 -->
<img class="lazy" data-echo="src/img/01.jpg" style="margin-top:500px" height="300px">
<!-- 3. 设置图片懒加载参数 -->
<script type="text/javascript">
echo.init({
offset: 500, // 离可视区域多少像素的图片可以被加载
throttle: 1000 // 图片延时多少毫秒加载
});
</script>
- Vue应用中使用图片懒加载,使用Vue-Lazyload 插件,参数如下:
参数 | 描述 | 默认 | 类型 |
---|---|---|---|
preLoad | 预加载高度的比例 | 1.3 | Number |
error | 加载失败时映像的src | 'data-src ’ | String |
loading | 加载时图像的src | ‘data-src’ | String |
attempt | 尝试计数 | 3 | Number |
listenEvents | 侦听的事件 | [ ‘scroll’ , ‘wheel’ , ’ mousewheel ’ ,’ resize ’ , ’ animationend ’ ,'transitionend ’ , 'touchmove ’ ] | Desired Listen Events |
adapter | 动态修改元素的属性 | { } | Element Adapter |
filter | 图像的侦听器过滤器 | { } | Image listener filter |
lazyComponent | lazyload 组件 | false | Lazy Component |
dispatchEvent | 触发dom事件 | false | Boolean |
throttleWait | 加载等待时间 | 200 | Number |
observer | 使用“观察者”接口 | false | Boolean |
observerOptions | 接口“观察者”选项 | { rootMargin: ‘0px’, threshold: 0.1} | IntersectionObserver |
silent | 不要打印调试信息 | true | Boolean |
// 1. 安装插件
npm install vue-lazyload --save-dev
// 2. main.js 引入插件
import VueLazyLoad from 'vue-lazyload'
// 在use中可以自定义参数
Vue.use(VueLazyLoad,{
preLoad: 1.3, // 预加载高度的比例
error:require('./statics/site/imgs/erro.jpg'),
loading:require('./statics/site/imgs/load.gif')
})
// 3. 修改图片显示方式为懒加载(v-lazy替换src属性)
<ul>
<li v-for="img in list">
<img v-lazy="'/static/img/01.jpg'">
</li>
</ul>
// or 原生HTML
<div v-lazy-container="{ selector: 'img', error: 'xxx.jpg', loading: 'xxx.jpg' }">
<img data-src="/static/img/01.jpg">
<img data-src="/static/img/02.jpg">
<img data-src="/static/img/03.jpg">
</div>
1.3.2 预加载
懒加载就是依赖提前执行。
当访问一个页面的时候,图片资源提前请求加载到本地,这样后面在需要用到时就直接从缓存取资源。当用户需要查看时可直接从本地缓存中渲染,滑动网页时有着极快的加载速度。这对图片占据屏幕很大比例的网站来说十分有利,它保证了图片快速、无缝地发布,有更好的用户体验。
示例:
示例代码转载于 前端优化——预加载篇
- 方式一:使用HTML标签
<img src="src/img/01.jpg" style="display:none"/>
- 方式二:使用Image对象
<script type="text/javascript">
var image= new Image()
image.src="src/img/01.jpg"
</script>
- 方式三:使用Ajax实现预加载,虽然存在跨域问题,但会精细控制预加载过程
var xmlhttprequest=new XMLHttpRequest();
xmlhttprequest.onreadystatechange=callback;
xmlhttprequest.onprogress=progressCallback;
xmlhttprequest.open("GET","http://image.baidu.com/mouse.jpg",true);
xmlhttprequest.send();
function callback(){
if(xmlhttprequest.readyState==4&& xmlhttprequest.status==200){
var responseText=xmlhttprequest.responseText;
}else{
console.log("Request was unsuccessful:"+xmlhttprequest.status);
}
}
function progressCallback(e){
e = e || event;
if(e.lengthComputable){
console.log("Received"+e.loaded+"of"+e.total+"bytes")
}
}