Bootstrap

前端技术概览

作者:兰峰

Web时代与浏览器的发展

Web时代

1990 年,英国计算机工程师蒂姆·伯纳斯·李(Tim Berners-Lee)在瑞士的欧洲核子研究组织(CERN)工作时,开发出首个 Web 服务器与图形化 Web 浏览器。他将这个进入互联网世界的新窗口,称为“WorldWideWeb”(即“万维网”)。这是一款为 NeXT 计算机开发的易于使用的图形化界面浏览器,超文本第一次通过公开网络被链接起来——即我们现在所熟知的 Web。

浏览器大战

网景崛起

要说浏览器的历史,要从1994年开始,那一年网景通信公司推出了代号为“网景导航者”的网景浏览器1.0(Mozilla Firefox的早期版本),随后迭代版本迅速占领浏览器大部分份额。

IE崛起

微软意识到网景通讯公司对其操作系统和应用市场的威胁,立马收购另外一家浏览器公司,在其基础上开发了Internet Explorer。在Windows浏览器中捆绑了IE浏览器,用户无需再掏钱买网景公司的浏览器,致使网景公司的浏览器市场份额大降。

IE不思进取

1998年1月,网景与微软IE浏览器竞争失利以后,为了挽回市场,网景通信公司公布旗下所有软件以后的版本皆为免费,并开放网景浏览器的源代码,成立了非正式组织Mozilla,自此Mozilla浏览器开始登上舞台。可惜的是尽管Mozilla(2002 年发布 Firefox)、Opera浏览器很好用,可微软操作系统的市场占有率很大,造成其他浏览器的市场份额一直不变。IE坐在份额第一的头把交椅后,却一直不思进取,自己制作一套Web标准,也不怎么支持HTML,Javascript,CSS这些Web技术的新版本特性,微软从IE6开始到IE8七八年间几乎没对浏览器做什么革新,大家都适应了IE,什么补丁、不安全、崩溃也不在意,也觉得浏览器就该如此。

Chrome崛起

2008年Chrome横空出世。界面简洁、加载快速、数据安全等这些特点让Chrome的市场份额逐步攀升。当微软意识到Chrome开始逐步侵蚀自己的市场时,开始频繁更新IE,2011年IE9发布,2012年IE10发布,2013年IE11发布,最后IE的代码实在适应不了新的要求的web技术,就重新开发了一个名为”edge"的浏览器用来取代IE,但还是挡不住Chrome成为市场份额第一的命运。

在IE横行的那一段时间为了适应IE中国的大多数常用网站也不大符合互联网标准,也就是说如果用符合互联网标准的浏览器去解析这些网站,反而会不正常显示,可见IE坐头把交椅的这几年,却一直在误导和阻挠互联网的发展。在此要向那些不断创新、不断完善、不断接纳新Web技术的浏览器公司致敬,面对IE他们的市场份额不高,却仍然坚持着不断前进。

国产浏览器起源

其实国产浏览器的起源于IE,一位网名为changyou(畅游)的程序员于1999年在论坛上发布一款叫”MyIE"的浏览器,基于IE,但采用多窗口浏览,占用系统资源比IE6少很多,且有鼠标手势、视觉化书签等功能,后来的中国浏览器MyIE2(后改名Maxthon)、网际畅游(后改名GreenBrowser)与TheWorld(世界之窗)等都是用MyIE的源代码改写完成。遨游成立公司独自运营,TheWorld被360收购变成了360安全浏览器。

一些国内互联网公司认识到浏览器是互联网的入口,是推广自家产品的最佳工具,如果大家都用了我的浏览器,那我在浏览器的显要位置放上自己的产品岂不是很容易,其次google创建了一个开源浏览器引擎Chromium项目,这样一来做一个简单、快速、安全的浏览器就很容易,为什么不做呢!同时为了适应国内环境还推出了双内核版本的浏览器,Chromium内核为极速内核,打开网页速度快,IE内核为兼容内核,为了兼容银行、政府部门的老网站访问。例如搜狗、360、QQ浏览器等等,无一不是套着不同的外壳用着相同的内核。

  • Google在2008年9月决定开源整个Chrome项目作为其Chromium项目的一部分。

  • Chromium 是 Google 的Chrome浏览器背后的引擎,其目的是为了创建一个安全、稳定和快速的通用浏览器,使用Chromium开源代码(基于webkit内核)的浏览器有360极速浏览器、枫树浏览器、太阳花浏览器、世界之窗极速版、UC浏览器电脑版、搜狗高速浏览器和qq浏览器等。 google一直坚持开源这个态度,Chromium和android一样开源的同时快速迭代产品,从而混乱现有格局,态度是好的,结果也不赖,一举两得。

微软放弃自有内核,拥抱Chrominum

2020年1月16日微软发布基于Chrominum的edge浏览器性能提升很大,可与Chrome媲美,使得Edge浏览器份额逐渐上涨。

PC浏览器内核

1997年 Trident(IE内核,在 1997 年的 IE4 中首次被采用并沿用到 IE11)

1998年 KHTML(由KDE所开发的HTML排版引擎)

2000年 Gecko(Netscape6 开始采用的内核,后来的 Mozilla FireFox(火狐浏览器) 也采用了该内核,Gecko 的特点是代码完全公开,因此,其可开发程度很高,全世界的程序员都可以为其编写代码,增加功能)

2001年 WebKit(KHTML的分支,苹果Safari)

2003年 Presto(挪威产浏览器 opera 的 "前任" 内核,为何说是 "前任",因为最新的 opera 浏览器早已将之抛弃从而投入到了谷歌大本营)2008年 Chromium(早期基于WebKit)2010年 混合引擎(双核)2013年 Blink(WebKit分支)

2015年 EdgeHTML(Edge 浏览器)

  • Blink 其实是 WebKit 的分支,如同 WebKit 是 KHTML 的分支。Google 的 Chromium 项目此前一直使用 WebKit(WebCore) 作为渲染引擎,但出于某种原因,并没有将其多进程架构移植入Webkit。后来,由于苹果推出的 WebKit2 与 Chromium 的沙箱设计存在冲突,所以 Chromium 一直停留在 WebKit,并使用移植的方式来实现和主线 WebKit2 的对接。这增加了 Chromium 的复杂性,且在一定程度上影响了 Chromium 的架构移植工作。基于以上原因,Google 决定从 WebKit 衍生出自己的 Blink 引擎(后由 Google 和 Opera Software 共同研发),将在 WebKit 代码的基础上研发更加快速和简约的渲染引擎,并逐步脱离 WebKit 的影响,创造一个完全独立的 Blink 引擎。这样以来,唯一一条维系 Google 和苹果之间技术关系的纽带就这样被切断了。

网络

在浏览器输入网址到浏览器展示页面经过了什么?

1.输入网址+回车

2.DNS解析得到 IP 地址(DNS查询过程:浏览器缓存、系统缓存、hosts文件、野生DNS服务器(本地DNS服务器)、根DNS、顶级DNS、权威DNS、本地(附近)CDN、源站)

3.得到后IP,通过IP地址去找目标服务器,路由器根据路由表路由转发

4.找到后进行TCP三次握手建立连接

5.连接成功发起HTTP请求

6.目标服务器响应HTTP请求(返回请求的HTML文件)

7.浏览器收到HTML文件后解析HTML代码,并请求HTML代码中的资源(如JS、CSS、图片等)

8.浏览器对页面进行渲染呈现给用户

TCP三次握手、四次挥手

为什么握手要三次,挥手要四次?因为TCP是基于全双工通信的,通信的两方都可以向对方发送消息和接收消息,在一方发起挥手后接收方有可能还有数据没有发送完成,只能先返回已经收到了对方的挥手,等数据发送完成之后再发起自己要关闭的请求,等到对方应答收到了就关闭连接。

内容分发网络加速静态资源——CDN(Content Delivery Network)

CDN是构建在现有网络基础之上的智能虚拟网络,依靠部署在各地的边缘服务器,通过中心平台的负载均衡、内容分发、调度等功能模块,使用户就近获取所需内容,降低网络拥塞,提高用户访问响应速度和命中率。

A记录

域名到IP

CNAME

域名到域名

CDN的作用位置

1.本地DNS向权威DNS发送DNS查询报文时;

2.权威DNS查找到一条NAME字段为“join.xx.com”的CNAME记录(由服务提供者配置),该记录的Value字段为“join.xx.cdn.com”;并且还找到另一条NAME字段为“join.xx.cdn.com”的A记录,该记录的value字段为GSLB(全局负载均衡系统)的IP地址;

3.本地DNS向GSLB发送DNS查询报文;

4.GSLB根据本地DNS的IP地址判断用户的大致位置用户的大致位置为广州,筛选出位于华南地区且综合考量最优的SLB(本地负载均衡系统)的IP地址填入DNS回应报文,作为DNS查询的最终结果;

5.本地DNS回复客户端的DNS请求,将上一步的IP地址作为最终结果回复给客户端;

6.客户端根据IP地址向SLB发送HTTP请求:“join.xx.com/video.php”;

7.SLB综合考虑缓存服务器集群中各个节点的资源限制条件、健康度、负载情况等因素,筛选出最优的节点后回应客户端的HTTP请求(状态码为302,重定向地址为最优缓存节点的IP地址);

8.客户端接收到SLB的HTTP回复后,重定向到该缓存节点上;

9.缓存节点判断请求的资源是否存在、过期,将缓存的资源直接回复给客户端,否则到源站进行数据更新再回复。

与普通DNS过程不同的是,这里需要服务提供者(源站)配置它在其权威DNS中的记录,将直接指向源站的A记录修改为一条CNAME记录以及对应的A记录,CNAME记录将目标域名转换为GSLB的别名,A记录又将该别名转换为GSLB的IP地址。通过这一系列的操作,将解析源站的目标域名的权利交给了GSLB,以至于GSLB可以根据地理位置等信息将用户的请求引导至距离其最近的“缓存节点”,减缓了源站的负载压力和网络拥塞。

为什么使用 CDN 需要 CNAME 记录?

举个例子:在某个图片云平台创建加速域名后,平台会给域名分配一个'CNAME域名'(例:cdn-example-com.test.com),我们需要在域名服务商中配置一条CNAME记录,将访问加速域名的请求指向这个cdn-example-com.test.com域名记录,生效后访问加速域名时解析将会指向加速的图片云平台CDN地址,之后由图片云平台的CDN完成调度,使得该域名所有请求都开始享有CDN图片加速效果。

为什么要有CNAME记录?

如果服务商给你一个ip,假如哪天服务商想把ip地址换一个,很多人域名上对应的ip地址就要跟着变化,要让所有人都一起改完是一件成本很高的事情,换成CNAME就没事了,你用你的CDN,他改他的ip地址。唯一的坏处就是,第一次DNS解析域名的时候会多解析一次。总体来看,好处远大于坏处。

常见的服务器响应状态码

  • 200:OK(没有问题)

  • 206:Partial Content(客户端使用Content-Range指定了需要的实体数据的范围,然后服务端处理请求成功之后返回用户需要的这一部分数据而不是全部)

  • 301:Moved Permanently(代表永久性定向。该状态码表示请求的资源已经被分配了新的URL,以后应该使用资源现在指定的URL)

  • 302:Found(代表临时重定向。该状态码表示请求的资源已经被分配了新的URL,但是和301的区别是302代表的不是永久性的移动,只是临时的。)

  • 304:Not Modifie(你要去拿缓存)

  • 307:Temporary Redirect(临时重定向,与302相同,但是302会把POST改成GET,而307就不会。)400:Bad Request(400表示请求报文中存在语法错误。需要修改后再次发送)

  • 403:Forbidden(有表明请求访问的资源被拒绝了。没有获得服务器的访问权限,IP被禁止等。)404:Not Found(服务器没有这个资源)

  • 500:Internal Server Error(服务器内部错误,表明服务器端在执行请求时发生了错误,很有可能是服务端程序的Bug或故障)

  • 503:Service Unavailable (它表示服务器尚未处于可以接受请求的状态)

HTTP和HTTPS的区别

HTTP

  • 80端口

  • 只需要TCP三次握手

HTTPS

  • 443端口

  • 在TCP三次握手基础上还要四个阶段的SSL / TLS的握手

  • 加密HTTPS采用的是对称加密和非对称加密结合的混合加密方式:

  • 在通信建立前,采用非对称加密的方式交换会话密钥,后续就不再使用非对称加密

  • 在通信过中全部使用对称加密的会话密钥的方式加密明文数据

浏览器

浏览器的缓存机制

浏览器缓存(Browser Caching)是为了节约网络的资源加速浏览,浏览器在用户磁盘上对最近请求过的文档进行存储,当访问者再次请求这个页面时,浏览器就可以从本地磁盘显示文档,这样就可以加速页面的阅览。

通常浏览器缓存策略分为两种:强缓存(Expires,cache-control)和协商缓存(Last-modified ,Etag),并且缓存策略都是通过设置 HTTP Header (Expires,cache-control,Last-modified ,Etag)来实现的。

基本流程:

  • 浏览器在加载资源时,根据请求头的expires和cache-control判断是否命中强缓存,是则直接从缓存读取资源,不会发请求到服务器;

  • 如果没有命中强缓存,浏览器一定会发送一个请求到服务器,通过last-modified和etag验证资源是否命中协商缓存,如果命中,服务器会将这个请求返回,但是不会返回这个资源的数据,依然是从缓存中读取资源;

  • 如果前面两者都没有命中,直接从服务器加载资源。

强缓存

1、Expires

Expires是http1.0提出的一个表示资源过期时间的header,它描述的是一个绝对时间,由服务器返回。

Expires 受限于本地时间,如果修改了本地时间,可能会造成缓存失效。

2、Cache-Control

Cache-Control 出现于 HTTP / 1.1,优先级高于 Expires ,表示的是相对时间。

协商缓存

当浏览器对某个资源的请求没有命中强缓存,就会发一个请求到服务器,验证协商缓存是否命中,如果协商缓存命中,请求响应返回的http状态为304并且会显示一个Not Modified的字符串。

1、Last-Modified,If-Modified-Since

Last-Modified 表示本地文件最后修改日期,浏览器会在request header加上If-Modified-Since(上次返回的Last-Modified的值),询问服务器在该日期后资源是否有更新,有更新的话就会将新的资源发送回来。

但是如果在本地打开缓存文件,就会造成 Last-Modified 被修改,所以在 HTTP / 1.1 出现了 ETag。

2、ETag、If-None-Match

Etag就像一个指纹,资源变化都会导致ETag变化,跟最后修改时间没有关系,ETag可以保证每一个资源是唯一的。

If-None-Match的header会将上次返回的Etag发送给服务器,询问该资源的Etag是否有更新,有变动就会发送新的资源回来。

整体流程:

存储

  • cookie(最大存储4k):可以设置生效的域名、路径、过期时间、过期秒数等,一般用于存储用户状态信息,cookie中的内容会根据设置域名随着HTTP请求一起发送到服务器,服务器可以获取cookie中存储的用户标记信息,判断该请求来自于哪个用户;seesion机制中的用户信息存放在cookie中;登录账号成功之后记录登录状态可以记录到cookie中,做到下次打开网站免登录的效果。

  • Storage(sessionStorage/localStorage)最大存储5M:localStorage为持久存储,除非手动清除,否则一直存在;sessionStorage为会话存储,即关掉当前页面sessionStorage中存储的数据就被浏览器清除了,再重新通过链接地址打开关闭页面,sessionStorage中的之前存储的数据已经不在,而localStorage中存储的还在。localStorage中也可以存储token信息,做到免登陆的效果(JWT)。

  • IndexedDB:是浏览器提供的本地数据库, 允许储存大量数据,提供查找接口,还能建立索引。这些都是 LocalStorage 所不具备的。就数据库类型而言,IndexedDB 不属于关系型数据库(不支持 SQL 查询语句),更接近 NoSQL 数据库。

跨域

跨域是什么?

浏览器的同源策略导致了跨域,其目的是隔离潜在恶意文件。

什么是同源策略?

同源策略可以防止JavaScript发起跨域请求。源被定义为协议、主机名和端口号的组合。此策略可以防止页面上的恶意脚本通过该页面的文档对象模型,访问另一个网页上的敏感数据。

解决跨域的方法:

1.jsonp,浏览器允许script标签加载不同源的资源

2.反向代理(NGINX服务器内部配置)

3.配置 CORS(跨域资源共享)前后端协作设置请求头部,Access-Control-Allow-Origin等头部信息4.iframe嵌套通信,postMessage

JSONP是什么?

CORS简单请求+预检请求

当一个资源与该资源本身所在的服务器不同的域、协议、端口请求一个资源时,资源会发起一个跨域HTTP请求。对于浏览器限制这个词:不一定是浏览器限制了发起跨站请求,也可能是跨站请求可以正常发起,但是返回结果被浏览器拦截了。

CORS跨域资源共享,该标准新增了一组HTTP首部字段,允许服务器声明哪些资源站通过浏览器有权限访问哪些资源。规范要求,对那些可能对服务器产生副作用的HTTP请求方法(特别是GET以外的HTTP请求,或者搭配某些MIME类型的POST请求),浏览器必须首先使用OPTIONS方法发起一个预检请求(preflight request),从而获知服务端是否允许该跨域请求。服务端允许后,才发起实际的HTTP请求。在预检请求的返回中,服务器端也可以通知客户端,是否需要携带身份凭证(包括Cookies和HTTP认证相关数据)。

什么是简单请求?

不会触发CORS预检的请求称为简单请求,满足以下所有的条件才会被视为简单请求,基本上我们日常开发只会关注前面两点:

1.使用GET POST HEAD其中一种方法

2.只使用了如下的安全首部字段,不得人为设置其他首部字段

o Accept

o Accept-Language

o Content-Language

o Content-Type 仅限以下三种

  • text/plain

  • mutipart/form-data

  • application/x-www-form-urlencoded

o HTML头部header field字段:DPR、Download、Save-Data、Viewport-Width、Width

3.请求中的任意XMLHttpRequestUpload对象均为没有注册任何事件监听器;XMLHttpRequestUpload对象可以使用XMLHttpRequest.upload属性访问

4.请求中没有使用ReadableStream对象

前端三剑客

HTML+CSS+JavaScript

HTML——网页的骨架(DOM树)

页面结构(DOM节点,DOM树的节点)

为什么移动站点叫H5?

H5是HTML5的简称。

现阶段的网页展示技术无论是移动端还是PC端都用到了HTML5,HTML5是相对于之前的HTML4的超文本标记语言规范。

之所以说移动站是H5站H5版本是因为在HTML5技术没有成熟之前,Web的移动端表现并不是很好,比如加载速度、响应速度、对手机内存要求高等,与原生的安卓iOS嵌套性能也不好,而HTML5技术成熟之后,性能和表现都很出色,加上现阶段的机动设备处理系统、手机内存都比之前好了很多,所以HTML5可以放肆的用在移动端,很多移动页面也就称呼为“H5”页面。

外行人一般会将移动端Web站点叫成H5,专业前端一般称其为Web移动端。

CSS——骨架(树节点)的大小、涂料、状态(动画)

美化+动画

JavaScript——让骨架移动,修改骨架的CSS,修改骨架显示的内容

元素移动动画+动作

写逻辑当什么时候操作DOM节点、让DOM节点怎样运动,修改DOM节点的属性如class,style

JavaScript版本

从 2015 年起,ECMAScript 按年命名,ECMAScript 通常缩写为 ES,ES6即为ECMAScript的第六个版本(ECMAScript 2015=ES6)。

JavaScript版本

浏览器支持

所有浏览器都完全支持 ECMAScript 3。

所有现代浏览器(Chrome、Firefox、Safari、Edge)都完全支持 ECMAScript 5。

JavaScript库的发展

jQuery时期(2009~2012年 石器时代)

jQuery的语法设计使得许多操作变得容易,如操作文档对象(document)、选择文档对象模型(DOM)元素、创建动画效果、处理事件、以及开发Ajax程序。

更方便地操作DOM

原生JavaScript操作DOM

// 通过css id获取dom元素

var oEl = document.getElementById('idName')

// 通过css 类名获取dom元素

var oEls = document.getElementsByClassName('className')

// 通过标签名获取dom元素

var oEls = document.getElementsByTagName('tagName')

// 查找元素并为该元素添加一个类

document.getElementById("myDIV").classList.add("myclass");

jQuery操作

// 通过css id获取dom元素

$('#idName')

// 通过css 类名获取dom元素

$('.className')

 

// 操作dom元素,支持链式调用

$('#idName').css({...}).find(...).css({})

jQuery的出现促使了以下两个JavaScript原生api的诞生

document.querySelector('#id')

document.querySelectorAll('.className')

更方便地发请求

原生JavaScript发起ajax请求

// get请求

var xhr = new XMLHttpRequest();

 

xhr.open('GET', '/api/user?id=333', true);

xhr.send();

 

xhr.onreadystatechange = function (e) {

  if (xhr.readyState == 4 && xhr.status == 200) {

    console.log(xhr.responseText);

  }

};

// post请求

var xhr = new XMLHttpRequest();

 

xhr.open('POST', '/api/user', true);

// POST请求需要设置此参数

xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded')

xhr.send('name=33&ks=334');

 

xhr.onreadystatechange = function (e) {

  if (xhr.readyState == 4 && xhr.status == 200) {

    console.log(xhr.responseText);

  }

};

jQuery发起ajax请求

$.ajax({

    url: "/greet",

    data: {name: 'jenny'},

    type: "POST",

    dataType: "json",

    success: function(data) {

        // data = jQuery.parseJSON(data);  //dataType指明了返回数据为json类型,故不需要再反序列化

        ...

    }

});

 

$.post(url, data, func, dataType);

 

$.get(url, data, func, dataType);

/*

可选参数:

1)url:链接地址,字符串表示

2)data:需要发送到服务器的数据,格式为{A: '...', B: '...'}

3)func:请求成功后,服务器回调的函数;function(data, status, xhr),其中data为服务器回传的数据,status为响应状态,xhr为XMLHttpRequest对象,个人感觉关注data参数即可

4)dataType:服务器返回数据的格式

*/

jQuery已经足够了吗?

虽然使用jQuery已经大大增加了操作DOM的效率,但是频繁地操作DOM会导致性能问题。另外在改变多个节点时需要选取多次(或者必须通过一个特殊的统一命名选取多个需要修改的元素),非常之麻烦。

Hello World

World

AngularJS的诞生2009(铁器时代)

一位 Google 工程师开源了他的业余项目 AngularJS,这个全新的框架给当时被 jQuery 统治的前端行业带来了革命性的进步。当时这位工程师只用 1,500 行 AngularJS 代码在 2 周内实现了 3 人 花了 6 个月开发的 17,000 行代码的内部项目。由此可见这个框架的强大。AngularJS 的主要特点是 HTML 视图(HTML View)与数据模型(Data Model)的双向绑定(Two-way Binding),意味着前端界面会响应式的随着数据而自动发生改变。这个特性对于习惯写 jQuery 的前端工程师来说是既陌生又期待的,因为这种编写模式不再要求主动更新 DOM,从而将节省大量主动操作 DOM 的代码和逻辑,这些 AngularJS 全部帮你自动完成了。

  

  

  


  

Hello {{yourName}}!

Angular的亮点:

  • 双向绑定:当修改一个输入框的值时,会修改绑定到这个输入框的变量。当修改这个输入框绑定的变量会修改输入框里面的值

  • 虚拟DOM:虚拟DOM是对页面元素真实DOM的一个抽象,在修改一个变量时,先修改虚拟DOM,经过Angular的脏检查机制,会判断哪个真实的DOM需要更新,最终只更新需要更新的真实DOM。对比原生JavaScript和jQuery,每次更新都需要开发人员手动去选择需要更新的真实DOM节点,Angular对开发的效率有很大的提升。

React的诞生2013年

支持双向绑定,通过diff算法计算出需要修改的虚拟DOM,再更新真实DOM

class App extends React.Component {

  constructor(props) {

    super(props);

    this.state = {message: 'World'}

    this.handleChange = this.handleChange.bind(this)

    this.handleClick = this.handleClick.bind(this)

  }

  handleClick() {

    this.setState({message: 'React'})

  }

  handleChange(e) {

    this.setState({message: e.target.value})

  }

  render() {

    return (

      

        

Hello {this.state.message}

{this.state.message}

        

        

      

    );

  }

}

ReactDOM.render(, window.root);

Vue的诞生2014年2月

吸取Angular和React的优点,同时实现了模板语法,增加了开发效率。

  

Hello {{ message }}

{{ message }}

  

  

const app = new Vue({

  el: '#root',

  data: {

    message: 'World'

  },

  methods: {

    handleClick() {

      this.message = 'Vue'

    }

  }

});

组件

组件是Vue最为强大的特性之一。为了更好地管理一个大型的应用程序,往往需要将应用切割为小而独立、具有复用性的组件。在Vue中,组件是基础HTML元素的拓展,可方便地自定义其数据与行为。下方的代码是Vue组件的一个示例,渲染为一个能计算鼠标点击次数的按钮。

// 定义一个名为 button-counter 的新组件

Vue.component('button-counter', {

  data: function () {

    return {

      count: 0

    }

  },

  template: ''

})

模板

Vue使用基于HTML的模板语法,允许开发者将DOM元素与底层Vue实例中的数据相绑定。所有Vue的模板都是合法的HTML,所以能被遵循规范的浏览器和HTML解析器解析。在底层的实现上,Vue将模板编译成虚拟DOM渲染函数。结合响应式系统,在应用状态改变时,Vue能够智能地计算出重新渲染组件的最小代价并应用到DOM操作上。

此外,Vue允许开发者直接使用JSX语言作为组件的渲染函数,以代替模板语法。[13]以下为可计算点击次数的按钮的JSX渲染版本(需配置相应Babel编译器):

Vue.component('buttonclicked', {

  props: ["initial_count"],

  data: function() {var q = {"count": 0}; return q;} ,

  render: function (h) {

    return ()

  },

  methods: {

    "onclick": function() {

      this.count = this.count + 1;

    }

  },

  mounted: function() {

    this.count = this.initial_count;

  }

});

响应式设计(双向绑定)

响应式是指MVC模型中的视图随着模型变化而变化。在Vue中,开发者只需将视图与对应的模型进行绑定,Vue便能自动观测模型的变动,并重绘视图。这一特性使得Vue的状态管理变得相当简单直观。

单文件组件为了更好地适应复杂的项目,Vue支持以.vue为扩展名的文件来定义一个完整组件,用以替代使用Vue.component注册组件的方式。开发者可以使用 Webpack或Browserify等构建工具来打包单文件组件。

核心插件

  • vue-route 前端路由

  • vuex 前端状态管理

  • vue-loader 将.vue文件转换成js文件

  • vue-cli 创建vue项目命令行工具

  • vite 开发工具

前端工程化

Nodejs的诞生2009

Java有JRE,JRE中有JVM,NodeJS中也有JavaScript虚拟机,这个虚拟机就是Chrome的开源JavaScript解析器——V8引擎。

NodeJS可以称之为服务器端的JavaScript,可以操作文件(修改、和生成新的文件),这为前端的工程化带来了革命性的进步。

NPM

npm为NodeJS包管理器,类似Java的Maven。

package.json文件

{

  "name": "xxx",

  "version": "2.4.2",

  "description": "xxxxx",

  "authors": [

    "xxxxx"

  ],

  "private": true,

  "engines": {

    "node": ">= 14",

    "npm": ">= 5.2.0"

  },

  "scripts": {

    "dev": "nuxt",

    "dev-mock": "nuxt --mock",

    "dev-joint": "nuxt --joint",

    "build": "nuxt build",

    "start": "nuxt start",

    "generate": "nuxt generate",

    "lint": "eslint --ext .js,.vue --ignore-path .gitignore .",

    "lintfix": "eslint --fix --ext .js,.vue --ignore-path .gitignore ."

  },

  "dependencies": {

    "@nuxtjs/axios": "5.12.2",

    "@nuxtjs/composition-api": "0.15.1",

    "@nuxtjs/google-analytics": "2.4.0",

    "@nuxtjs/proxy": "^2.1.0",

    "@nuxtjs/pwa": "3.2.2",

    "@nuxtjs/style-resources": "0.1.2",

    "@vue/babel-preset-jsx": "1.2.4",

    "dayjs": "1.8.18",

    "js-base64": "^3.6.0",

    "js-cookie": "^2.2.1",

    "less": "3.9.0",

    "less-loader": "4.1.0",

    "nuxt": "2.14.7",

    "qs": "^6.9.4"

  },

  "devDependencies": {

    "@babel/core": "7.8.3",

    "@babel/plugin-proposal-nullish-coalescing-operator": "7.8.3",

    "@babel/plugin-proposal-optional-chaining": "7.8.3",

    "@babel/preset-env": "7.12.1",

    "@commitlint/cli": "8.2.0",

    "@commitlint/config-conventional": "8.2.0",

    "babel-eslint": "10.1.0",

    "babel-jest": "24.9.0",

    "core-js": "3.7.0",

    "cross-env": "latest",

    "eslint": "7.13.0",

    "eslint-config-prettier": "6.15.0",

    "eslint-friendly-formatter": "4.0.1",

    "eslint-plugin-jest": "24.1.3",

    "eslint-plugin-nuxt": "1.0.0",

    "eslint-plugin-prettier": "3.1.4",

    "github-release-notes": "0.17.1",

    "husky": "1.3.1",

    "jest": "24.9.0",

    "lint-staged": "8.2.1",

    "prettier": "1.18.2",

    "standard-version": "6.0.1",

    "stylelint": "9.10.1",

    "stylelint-config-standard": "18.3.0",

    "svg-sprite-loader": "^4.1.6"

  }

}

  • dependencies:记录这个前端项目中所需要使用的外部模块

  • devDependencies:记录这个前端项目构建阶段所使用的模块

JavaScript模块化

模块化一些专业的定义为:模块化是软件系统的属性,这个系统被分解为一组高内聚,低耦合的模块。

模块化是前端工程化的基础,在没有模块化之前前端的项目文件夹大概是这样的

  

  

  

  Document

  

  

  main

  

  

我们会将公共的逻辑抽取到一个common.js中,在每个page-n.html子页面手动引入,这样会导致的问题:

  • 一个逻辑可能会在很多个页面使用,当项目非常大的时候,如果你把所有逻辑都放到common.js文件中会造成这个文件变得很大,如果某个页面只需要使用这个文件中的一两个方法时,就必须得全量引入common.js文件,会造成前端加载性能问题。

  • 在不依赖后台服务器端渲染的情况下,没办法提取出一个既带有JS逻辑又带有HTML页面的子模块进行复用,例如:网站的顶部导航模块、菜单模块等。

CommonJS

CommonJS是服务器端模块的规范,Node.js采用了这个规范。根据CommonJS规范,一个单独的文件就是一个模块。加载模块使用require方法,该方法读取一个文件并执行,最后返回文件内部的exports对象。

// foobar.js

 

//私有变量

var test = 123;

 

//公有方法

function foobar () {

 

    this.foo = function () {

        // do someing ...

    }

    this.bar = function () {

        //do someing ...

    }

}

 

//exports对象上的方法和变量是公有的

var foobar = new foobar();

exports.foobar = foobar;

//require方法默认读取js文件,所以可以省略js后缀

var test = require('./boobar').foobar;

 

test.bar();

CommonJS 加载模块是同步的,所以只有加载完成才能执行后面的操作。像Node.js主要用于服务器的编程,加载的模块文件一般都已经存在本地硬盘,所以加载起来比较快,不用考虑异步加载的方式,所以CommonJS规范比较适用。但如果是浏览器环境,要从服务器加载模块,这是就必须采用异步模式。所以就有了 AMD CMD 解决方案。

AMD和RequireJS

AMD是"Asynchronous Module Definition"的缩写,意思就是"异步模块定义"。AMD 是 RequireJS 在推广过程中对模块定义的规范化产出。

AMD设计出一个简洁的写模块API:

define(id?, dependencies?, factory);

第一个参数 id 为字符串类型,表示了模块标识,为可选参数。若不存在则模块标识应该默认定义为在加载器中被请求脚本的标识。如果存在,那么模块标识必须为顶层的或者一个绝对的标识。

第二个参数,dependencies ,是一个当前模块依赖的,已被模块定义的模块标识的数组字面量。

第三个参数,factory,是一个需要进行实例化的函数或者一个对象。

定义AMD模块

define("alpha", [ "require", "exports", "beta" ], function( require, exports, beta ){

    export.verb = function(){

        return beta.verb();

        // or:

        return require("beta").verb();

    }

});

模块加载

require([module], callback)

AMD模块化规范中使用全局或局部的require函数实现加载一个或多个模块,所有模块加载完成之后的回调函数。

[module]:是一个数组,里面的成员就是要加载的模块;

callback:是模块加载完成之后的回调函数。

// 加载一个math模块,然后调用方法 math.add(2, 3);

require(['math'], function(math) {

 math.add(2, 3);

});

define 和 require 这两个定义模块,调用模块的方法合称为AMD模式,定义模块清晰,不会污染全局变量,清楚的显示依赖关系。AMD模式可以用于浏览器环境并且允许非同步加载模块,也可以按需动态加载模块。

CMD和SeaJS

CMD是SeaJS 在推广过程中对模块定义的规范化产出,即SeaJS是CMD的实现。

  • 对于依赖的模块AMD是提前执行,CMD是延迟执行。不过RequireJS从2.0开始,也改成可以延迟执行(根据写法不同,处理方式不通过)。

  • CMD推崇依赖就近,AMD推崇依赖前置。

  • AMD 的 API 默认是一个当多个用,CMD 的 API 严格区分,推崇职责单一。比如 AMD 里,require 分全局 require 和局部 require,都叫 require。CMD 里,没有全局 require,而是根据模块系统的完备性,提供 seajs.use 来实现模块系统的加载启动。CMD 里,每个 API 都简单纯粹。

UMD

UMD是AMD和CommonJS的糅合,AMD 浏览器第一的原则发展异步加载模块。

CommonJS 模块以服务器第一原则发展,选择同步加载,它的模块无需包装(unwrapped modules)。

这迫使人们又想出另一个更通用的模式UMD (Universal Module Definition)。希望解决跨平台的解决方案。

UMD先判断是否支持Node.js的模块(exports)是否存在,存在则使用Node.js模块模式。再判断是否支持AMD(define是否存在),存在则使用AMD方式加载模块。

打包构建

自从有了node之后,单个的js文件离开了html以后也可以在终端run起来了,我们前端可以和别的语言一样在命令行里玩编程!模块化标准实施之后,js就有了“引入”和“导出”的概念,这带来的革命性变化便是:当我们写业务的时候再也不用很麻烦地去在html里写15个