0%

AMD,CMD,CommonJs,ESModule等规范简介

1. AMD

AMD 是 RequireJS 在推广过程中对模块定义的规范化产出,它是一个概念,RequireJS是对这个概念的实现

1
2
3
4
5
6
7
8
define(['package/a.js'], function(a) {
function log() {
a.log()
}
return {
log
}
})

关键词:依赖前置,所需依赖在最开头定义好

2. CMD

CMDSeaJS在推广过程中对模块定义的规范化产出,是一个同步模块定义,是SeaJS的一个标准

1
2
3
4
define(function(require, exports, module) {
const a = require('package/a.js')
a.log()
})

关键词:依赖就近引入,在哪里用就在哪里引

3. CommonJs

CommonJS规范– -是通过module.exports定义的,在前端浏览器里面并不支持module.exports,通过node.js后端使用的

4. ES6 Module

通过export,import对模块进行导出导入的

关键词:编译时就能确定模块的依赖关系,只会引入模块需要的依赖,多次import统一模块,只加载一次,加载性能更高。

DNS预解析

浏览器默认会对页面中和当前域名不在同一个域的域名进行预获取,并且缓存结果,这就是隐式的 DNS Prefetch。

如果想要禁用隐式的DNS prefetch ,可以使用以下的标签:

1
< meta  http-equiv="x-dns-prefetch-control" content="off">

如果想对页面中没有出现的域进行预获取,那么就要使用显示的 DNS Prefetch 了。

1
< link  rel="dns-prefetch" href="//www.zhix.net">

DNS Prefetch 应该尽量的放在网页的前面,推荐放在 <meta charset="UTF-8"> 后面

缓存策略之强缓存和协商缓存

浏览器缓存策略有强缓存和协商缓存

强缓存

通过 max-agepublicimmutable三个字段来控制强缓存策略

  • max-age:控制缓存过期时间
  • public:控制是否可被代理服务器缓存
  • immutable:控制缓存是否认为是不会改变的

组合如下:

  • cache-control: max-age=123 public immutable // 123秒内 客户端和代理服务器都可以缓存 假设不会改变(刷新浏览器也会取缓存)
  • cache-control: max-age=123 private // 123秒内 客户端可以缓存,代理服务器不能缓存 刷新浏览器不会取缓存
  • cache-control: no-cache 不使用强缓存,但是不妨碍设置协商缓存
  • cache-control: no-store 强缓存和协商缓存都不使用

协商缓存

比如我们强缓存设置了3天后过期,过期了以后,就会走真实的网络请求去服务端拿资源,但是服务端的资源是否真的过期了?如果没过期,不就浪费了网络io吗,为了解决这个问题,协商缓存出现了。

协商缓存通过两个字段:etag、last-modified来判断资源是否真的过期

  • etag:每个文件有一个,文件的hash,改动文件了就变了
  • last-modified:文件的上次修改时间,精确到秒

这两个字段在第一次真实请求资源时会由服务端设置,下次请求时浏览器会自动带上,通过这两个字段可以判断下次请求的资源是否在服务器上真的改变了。如果没变会返回304,客户端就会用老的资源

为什么要有etag?

你可能会觉得使用last-modified已经足以让浏览器知道本地的缓存副本是否足够新,为什么还需要etag呢?

etag是HTTP1.1中新增的,为了解决之前只有If-Modified的缺点,主要是为了解决如下几个last-modified的问题:

  1. 一些文件也许会更改,但是他的内容并没有改变,仅仅是修改时间改变
  2. 某些文件修改非常频繁,比如在秒以下的时间内进行修改,(比方说1s内修改了N次),if-modified-since能检查到的粒度是秒级的,这种修改无法判断
  3. 某些服务器不能精确的得到文件的最后修改时间。

所以,etag作为文件的hash,可配合last-modified完成更细粒度的缓存。

image

Tree Shaking,是代码优化重要的一项,可以理解为通过工具”摇”我们的JS文件,将其中用不到的代码”摇”掉,是一个性能优化的范畴。

  • Tree Shaking本质是一种静态代码分析,将不用的静态代码模块,剔除出去。这就代表了,只有模块代码才会被剔除,而且只有es的模块类型,是编译后执行(虽然js是解释型语言,但其实es6模块化后,有预编译的阶段)预编译阶段引用模块,才能实现静态分析。如果是CommonJS规范模块的代码,模块是执行时才引用的,比如require(‘…’),则是不能Tree Shaking的。举个例子:

这个时候,如果使用Tree Shaking,则可以剔除不用的模块b。只保留a模块,但是a模块内的无用代码,则不会被剔除。

再进一步,如果index.js中的代码是这样的:

a和b模块都引入了,但是b没有调用,则b也不会被剔除,所以Tree Shaking优化也是有限的,所以我们在代码开发的时候,要结合一些代码检查工具,移除代码中不用的模块,来进一步实现代码优化。

REST api

  • REST api简单来说就是将服务器资源定向到每个api上,比如localhost:8000/posts/1,对应查找文章的第一个,localhost:8000/comments/3,表示查找评论的第三条。REST的表达方式很简单,就是一条资源对应一个api,简洁明了。

    GraphQL api

  • 想象一个场景,前端需要查询一个用户的name,age,和用户发表的文章列表。传统api怎么设计?后端设计两个接口,一个查询用户信息,一个查询用户文章。前端调这两个接口,获得返回的信息,再把两个接口的数据组装起来。
  • 能不能将上面的场景简化一些呢?前端只需要调一个接口,就可以查出用户的信息和用户的文章。
  • GraphQL 出现了。GraphQL api是一种特别灵活的api设计,他将传统的查询逻辑,交给了客户端。用代码举一个例子:
    `javascript
    var express = require(‘express’);
    var graphqlHTTP = require(‘express-graphql’);
    var { buildSchema } = require(‘graphql’);
    var schema = buildSchema(type Query { name: String url: String, info: String });

var root = {
name: () => ‘zlx’,
url: () => ‘https://avatars1.githubusercontent.com/u/22437181?v=4',
info: () => ‘a frontend developer’
};

var app = express();
app.use(‘/graphql’, graphqlHTTP({
schema: schema,
rootValue: root,
graphiql: true,
}));
app.listen(4000, () => console.log(‘Now browse to localhost:4000/graphql’));

name: String
url: String,
info: String

上面代码,在前端执行了查询,只查询name属性:
image

如果我要查询name,url和info三条信息呢?
只需要在前端查询语句上加上对应的字段:

image
⚠️注:上面代码实现是javascript语言的GraphQL 实现,其他语言实现参考这里
这就是GraphQL api,它使接口请求更加灵活,更加适用于复杂场景,也使后端查询设计更加简单,后端工程师只需要定义好数据库和相关的表结构的Schema,所有查询逻辑交给前端来处理,使开发变得高效。

再说js中this的指向

js中,this的指向一直是一个难点,新手很容易搞糊涂,其实只需要记住一个规则就行了,规则先不说,看了下面的例子你就明白了:

上面的代码,执行obj.b()后,打印出了obj对象,{a: 100, b: ƒ}证明此时this的指向时obj对象,这个很好理解。

加以修改:

这段代码,将obj.b赋值给一个some变量,现在some变量就 变成了一个可执行函数,执行一下结果看到,this打印的是window对象。Window {postMessage: ƒ, blur: ƒ, focus: ƒ, close: ƒ, parent: Window, …}这就尴尬了,为什么函数里面打印的this变了呢?这是因为,js执行的时候,this的指向是指向它执行时的上下文的,我们看,上面代码,首先是定义了一个some变量,注意这个some变量是定义在window对象上的,此时some的执行上下文就是window,再把obj.b赋予给了some,此时some变成了一个函数,所以再执行some时,函数内的执行上下文,就是some所在的上下文,也就是window了。

不信邪了,那如果我不在window对象上定义一个some呢?此时this又指向哪里呢?

现在做了什么操作?我不再把some定义在window里面了,我把some定义在了一个oth对象里面,同样将ob j.b赋值给some,现在指向some的话,是什么结果呢?{some: ƒ}可见,this指向了oth对象,好像明白了点什么,根据上面的分析,this指向它执行时的上下文,它执行时,是作为oth的some字段的属性执行的,所以它的this就指向了oth对象。

明白了,以上就是js语言this的机制。感觉有点怪怪的,如果我每次调用,总是想让this指向最原始的对象呢?也可以,用call或者apply就可以实现:

指向上面代码,就可以看出,上面的this,通过call函数,都被手动指向到obj中去了。apply也有同样功能,有一些细微差别,就不细说了。

箭头函数是es6新增的对于函数的扩展,实现了函数写法简洁化,也使函数更具语义化,常见的箭头函数如下:

箭头函数可以省略函数代码块的大括号{},箭头后面的内容即为函数返回的内容,上面函数接受一个str,返回一个字符串拼接。
但是箭头函数使用中需要注意几个容易出现的错误:

  1. 如果箭头函数需要返回一个对象? 上面这样写对吗?箭头后面返回的是一个对象?其实这样写会报错,因为对象的大括号根函数体的大括号混淆了,执行的时候,函数会将对象的大括号当成函数体的大括号来解析,这样就会报语法错误,正确的写法是: 将要返回的对象,用小括号包起来,这样就不会产生混淆。
  2. 箭头函数内部使用arguments对象:

我们在普通函数中,经常使用arguments对象来获取函数的所有参数,但是在箭头函数中,arguments对象是未定义的,这时候我们可以使用…rest来获取参数:

  1. 箭头函数内部this指向函数外部,不指向箭头函数本身: 执行结果如下:
    image
    可见箭头函数this指向外部函数。

1.元素的可拖拽属性

html5中增加了元素拖拽的支持,只需要增加一个属性draggable:true后,元素就被赋予了可拖拽的能力。

当元素可拖拽后,可给元素添加6类拖拽事件监听:

  • dragstart,开始拖拽事件。
  • dragenter, 拖拽进入事件。
  • dragleave, 拖拽离开事件。
  • dragover, 拖拽over事件。
  • drop,拖拽释放事件。
  • dragend, 拖拽结束事件。

    2. 拖拽中的dataTransfer对象。

    dataTransfer对象,就是元素拖拽事件中的拖拽实例对象。

如下,实现一个拖拽元素:

3. DataTransfer的属性和方法

DataTransfer对象包含下面5个标准属性和4个标准方法。

标准属性

DataTransfer.dropEffect
获取当前所选拖放操作的类型,或将拖拽操作设置为新类型。值必须为none,copy,link或move中的一个。
DataTransfer.effectAllowed
提供可能的所有类型的操作。必须是none,copy,copyLink,copyMove,link,linkMove,move,all或uninitialized中的一个。
DataTransfer.files
拖拽的本地文件列表。如果拖动操作不涉及拖动文件,则此属性为空列表。
DataTransfer.items (只读)
提供DataTransferItemList对象,该对象是所有拖动数据的列表。
DataTransfer.types(只读)
在dragstart事件中设置数据格式,返回的是一个字符串数组。

标准方法

DataTransfer.clearData([format])
删除与给定类型关联的数据。format参数是可选的。如果类型为空或未指定,则删除所有关联的数据。如果指定类型的数据不存在,或者数据传输不包含任何数据,则此方法无效。
DataTransfer.getData(format)
返回给定类型的数据,如果该类型的数据不存在或数据传输不包含数据,则返回空字符串。
DataTransfer.setData(format, data)
设置给定类型的数据。如果该类型的数据不存在,则在末尾添加,以使列表中的最后一项成为新格式类型。如果该类型的数据已存在,则在相同位置把现有数据替换掉。
DataTransfer.setDragImage(img, xOffset, yOffset)
设置用于拖动的自定义图像。

下面用元素拖拽实现一个拖拽上传的demo:

upload4

demo代码仓库
用拖拽实现一个拖拽列表功能:

drag

demo代码仓库

了解重绘和回流之前,先了解以下几个知识点

1. DOM树

DOM叫文档结构模型。浏览器把网页中的html代码解析成一个Dom树,html中的每个tag都是Dom树中的1个节点,根节点就是我们常用的document对象 。dom树里面包含了所有的html tag,包括display:none隐藏的节点opacity:0的节点和用JS动态添加的元素等。
image

2. CSSOM树

CSSOM叫样式结构模型。浏览器把所有样式(主要包括css和浏览器的样式设置)解析成样式结构体,在解析的过程中会去掉浏览器不能识别的样式,比如IE会去掉-moz开头的样式。

3. render树

DOM树和CSSOM树结合以后,就生成render树,但是render树会抛弃head节点和display:none的节点,因为这些节点都 不会参与render,所以render树中会抛弃这些节点(opacity:0的节点会保留,因为他仍然占位)

image

现在介绍什么叫回流和重绘:

回流

回流,也叫重排,render树,需要绘制成一张网页,那么render树中所有节点,都需要根据style中的样式,排列到网页中指定位置中去,呈现出来才是正确的网页。这个排列的过程就叫回流,也叫重排。

重绘

在render树回流的过程中,不仅需要将style中决定位置的样式排列到网页指定位置,还需要将style中决定颜色,字体,opacity显隐等这些属性渲染,使网页有颜色有样式。这一过程,就叫做重绘。

所以得出结论,重绘只是绘制改变了元素的颜色,字体等,并无元素位置的绘制。并且,render树执行回流,会自动执行重绘。

举个形象的例子,我们在学校开运动会,观众坐在看台上,有时需要用颜色板拼一些加油助威的图案,那么,观众进场按位置落座这一过程,可以理解为回流(重排),观众举起颜色板,或者更换颜色板,会生成新的图案,但是每个观众位置并没改变。这时候,可以理解为重绘。

image