存档2019-03-31

前端优化,提高网页加载速度的思路与技巧汇总

网页的加载速度直接影响到用户的体验,尤其是对于新用户,这种影响非常明显。但是网页的加载速度受到网络,服务器和浏览器等多方面的影响,所以网页优化不仅仅涉及后端开发,前端同样“大有可为”,所以本文在前端的角度来探讨网页优化思路和技巧。

网页加载慢的后果

1 严重影响直接用户体验

这个每个人都有很直观的体会,如果一个网页半天打不开,绝大部分人都会选择直接关闭。也许数字更具说服力:有统计表明,如果页面超过3秒没有加载出来,那么至少会白白丢失40%以上的用户,所以给出的建议让用户能够等待的时间不要超过2秒。

2 影响在SEO上的表现

搜索引擎爬虫总是尽量模仿人的行为从而提高其自身的服务水平,如果一个网页加载很慢,就会影响到网页在SEO上的排名,这回简洁影响你网页的曝光量。

网页加载的过程

一个完整的页面加载过程,包括客户端(浏览器)发送请求、网络传输、服务器接收请求处理并返回数据、数据经过网络传输、客户端接收数据并渲染呈现。所以网页的加载速度受到网络,服务器性能,客户端性能等多方面的影响,所以网页加载速度的提升,不仅仅是后端服务器的事情,前端仍然大有可为。

前端能做什么

在提高网页加载方面,前端主要要从以下几个方面入手:减少代码大小、优化代码结构、优化网络请求、页面异步延迟加载。

减少代码大小

包括html、css、js代码文件的大小,又包括两个方面:

第一是精简代码,提升性能。在确保功能和性能等软性要求的前提下,采用更简洁的代码实现方式,也就是说能用一行代码搞定的就不用两行,包括不必要的html标签嵌套,css代码、js代码,这对前端编程的要求较高,另外js局部变量的查找速度比全局变量快的多,所以js中尽量使用局部变量而非全局变量。第二个是优化代码。在编码过程中,为提高易读性,必然带有许多空格,注释,或者其他冗余的代码,使用代码压缩工具,快速删掉逗号、注释甚至不需要的空格,可以显著压缩 JavaScript 代码大小,这类的工具有Google Closure Compiler 等。不仅仅是js代码可以用工具压缩,css和html代码同样可以压缩,工具也有很多。除了本身代码的优化,在使用第三方库的时候,要记得删除一些不必要的组件。第三是优化图片资源图片本身比文字(代码)占有更大体积,但是一图胜千文,图片的使用可以极大的美化页面,所以图片还是要用的,但是在保证美观的前提下,我们可以对图片进行优化,寻求美观与速度之间的平衡。

第一就是图片压缩,减小图片本身的大小。压缩分为有损压缩和无损压缩,各有利弊,这里不再细说了。具体方法可以下载专门的工具,也可以在线压缩。

第二是如果可以用CSS样式来替代图片,那就尽量用CSS替代图片。现在随着CSS3的成熟,一些特殊的形状和效果都可以实现了,但这各很考验CSS的功底哈。

第三是使用图片的时候的小细节,能用background使用图片就尽量不用img标签来加载图片,另外如无必要,能使用png8就不使用gif格式。

第四就是图片预加载和延迟加载技术,比如使用缩略图,默认占位符替代图片,但不影响后续页面内容的呈现。

第五是有些情况下,可以将图片转base64,将图片变成字符串,这样可以减少http请求,但是这个仅适用一些体积较小的图片,比如图标,不然转换后的字符串会比较大,得不偿失。

优化代码结构

页面是按照从上到下的顺序加载页面内容的,首先 <head> 部分中的所有内容都会在在你看到页面内容(body标签里的内容)之前加载完成。js的加载会中断所有其他元素的加载,正常情况下,只有在js加载完成之后,才会继续加载后续css和DOM元素,所以在 <head> 部分中使用 JavaScript 会导致延迟页面内容的呈现。

对此有两个方法。1 将样式表放在头部,如非必要,则将脚本放在底部。2 如果必须放在head标签里,可以采用async 标记,这样在加载js的过程中,不会中断后续CSS和DOM的加载,以及其他JS的运行。

优化网络方面请求

1 减少网络请求次数

每次加载页面的时候,除了嵌入页面本身的css,js代码,其他独立的每个css、js文件、图片文件都会产生一个网络请求。可以通过对多个CSS文件,js文件的合并来减少文件的个数。对于图片则可以使用雪碧图的形式,将

2 减少域名DNS查找

DNS查找是网络请求的第一步,浏览器只有根据域名找到对应的IP地址才能找到目标服务器,这个查找IP的过程如果耗时比较久,就会影响到网页的请求速度,所以需要适当降低DNS的时间设置。

3 Cookie的管理,使用缓存,设置期限

在Cookie的管理上, 减小Cookie大小,设置合理的过期时间能可以有效的提高网页内容加载速度。

4 使用CDN服务也是选项之一。

曲线救国——延迟加载

通常情况下,需要等网页的所有内容加载出来才会呈现,但是如果页面的内容确实较多,比如一些综合性的门户网站,那用户等候的时间肯定就得非常长了。而实际上,用户最先看多的只有一个屏幕那么多的内容,所以只需要在最短的时间内,将第一屏或者前几屏的内容呈现出来就可以了,其他的内容可以在用户浏览第一屏内容的时候继续加载,类似上面提高的图片延迟加载的方法,页面异步延迟加载,可以让别人“感觉起来”加载速度很快,从而提高用户体验。

以上是一些提高网页加载速度方面思路和技巧的总结,水平有限,如果有遗漏或者更好更新的方法,欢迎指正与交流!

全面理解页面CSS布局:定位,布局,盒模型

CSS就像个API,只要照着说明书,怎么地也能凑出一个像样的页面。如果正面凑不出来,通过各种属性的调整,也能绕路把它实现。CSS的结果就是可视化的页面,一旦效果出来,就感觉自己“掌握”了CSS,一心死磕JS去了 。这或许是大多数新晋前端都有的经历,也是导致后来写页面让然感觉困难重重的原因,因为那并不是掌握了CSS,只是会用了CSS的属性堆页面,没有理解其背后的原理。

用从PC端页面的布局说起。我们那边的土话说,凡事都有章法,写页面CSS的章法就是:先整体后局部。这个整体就是布局,不是仅仅指html的标签配置,更是样式上的布局,主要有:定位、浮动、尺寸。

浏览器渲染页面的过程其实跟我们画一张画的步骤几乎一样,画哪个位置,哪里开始落笔,画多大。这几个完成,基本上布局就完成了,定位、浮动、尺寸(盒模型)刚好解决了这三个问题。

定位

定位的意思确定自己的位置,但是是相对于另外一个参照物(比如上级元素或者窗口)来确定自己位置。这里有个基础知识是标准文档流,在标准文档中,任何一个html元素都会有一个默认的位置,这个是定位的基础,是定位的参照物。

定位的属性名称是:position,属性值目前有6个:static、inherit、relative、absolute、fixed、sticky。

static(默认值):元素框正常生成。这个时候top, bottom, left, right即使有值,也会被忽略 。

inherit :继承,规定应该从父元素继承 position 属性的值。

relative:元素框相对于自身在标准文档流中的位置发生偏移,但不脱离文档标准流,位置保留。此时设置top、right、bottom、left四个属性会生效,其相对的参照物就是自身在文档标准流的位置。

absolute:脱离标准文档流,后面的元素会补位。此时设置top、right、bottom、left,并且往上相对其所有级别中第一个position值不为static的父级元素,直至相对于窗口。

fixed:脱离标准文档流,并且相对于视窗进行定位。经常用到的地方必须一直贴在页面顶端的导航,侧边的工具栏等。比如我现在在用的百家号的编辑器工具栏。

sticky:粘性定位,是css3新增的属性值, 可以理解是relative和fixed混合使用。最初会被当作是relative,相对于原来的位置进行偏移;一旦超过一定阈值之后,会变成fixed定位,相对于视口进行定位。

浮动

浮动是不同与定位的一个属性,名称float,属性值有left、right、none、inherit。none就是正常的标准文档流,inherit指继承自父级。

浮动是和很奇葩的存在,你说他脱离文档流了,但是他的内容好像又还在原来的文档流占着位置,因为他后面的元素都是在浮动元素的内容之后开始布局,这曾经让无数工程师百思不得其解。

设置DIV1的float:left,DIV2元素补位,左上角开始点与DIV1开始点重合,但是DIV2的内容元素并没有按照正常理解的在DIV2的开始点开始,而是在DIV1之后开始,仍然有环绕的效果。所以让然对后续元素的布局有影响。

说到浮动,不能不提的是清除浮动。清除浮动需要用到clear属性,在浮动元素的背后加一个空元素然后设置clear的属性,可以达到效果。但是添加元素的方法太过low,代码也不简洁,所以比较常用的是使用after伪类:对子元素的after伪类设置clear属性值。另外一个方法是将父元素的overflow设置成hidden。

尺寸(盒模型)

尺寸就是确定自身的大小。这里有两个知识点,一个是自身是什么形式存在的,第二个是大小的单位是什么。

先第一个问题,答案是盒模型。每个元素都是以矩形块的形式存在的,不是圆形或者其他什么形状。css中存在两种不同的盒子模型,标准盒子盒子模型和box盒子模型,通过box-sizing属性设置不同的模型。如图:

这两个模型的主要区别是width的宽度不同,当设置box-sizing:border-box时,盒子模型的width=border+padding+content的总和,也就是除了margin之外的所有宽度之和:

当然,布局的鼻祖table,除了一些要EDM中建议使用,现在很少用于布局了。以上是关于布局的为一个整体的介绍。让写页面能够游刃有余,还得将知识的理解用在敲的代码里。比如实现有自适应要求的两栏布局和三栏布局,你有几种方法呢?

是难点也是基础:js执行上下文及其运行原理

在生活中,同样的文字放在不同的场合,会有不同甚至完全相反的意思,比如让人头疼的汉语四六级(搞笑)。这个说话的场合,其实就是我们说话的语境,也是说话谈论的前提。同样的,对程序语言进行“解读”的时候,也必须在特定的语境中,这个语境就是javascript中的执行上下文。

执行上下文是什么?

以上是javascript上下文的通俗理解,标准的说法是,执行上下文(Execution Context)是javascript代码执行的环境,js代码一定是在执行上下文中执行的。

在js中,执行上下文的类型有以下三重:

全局执行上下文

全局执行上下文是JS代码最开始运行时的默认环境。如果是浏览器的话,就会创建一个全局的window对象,这也是当前页面所有js代码的基础环境,一个页面只会有一个全局执行上下文。

函数执行上下文

程序中有许多的函数,函数在被调用的时候就会创建对应的执行上下文,而且是每次调用都会创建新的执行环境。

eval函数执行上下文

js的eval函数执行其内部的代码会创建属于自己的执行上下文,已不被推荐使用,所以在这里仅作了解,不再详细讨论它了。

综合起来,从执行上下文的角度来看js运作过程就是:当页面加载时,JavaScript运行时首先会进入全局环境,生成全局上下文(一个)。程序中各种函数的依次调用,就会进入函数执行环境,对应就会生成该函数的执行上下文。

JS中管理多个执行上下文的方式

以上可知,js代码运行过程中除了最开始的全局执行上下文,还有有各种函数的调用产生的函数执行上下文,那么js是怎么管理这些上下文的呢?

首先,我们要知道的之前提到的一个知识,js是单线程的。意思是js不能同时干两件事情,必须是一个一个的执行。那么这么多的执行上下文是必然要按一定的顺序执行,这个顺序是怎么决定的呢? 现实中解决这类问题的方法是叫号,摇号,但js中这么整就完全乱套了。因为js的函数调用是有逻辑关系的,那如何保证这种逻辑关系能够正确执行呢?

答案是:栈。

在JavaScript中,通过栈的存取方式来管理执行上下文,我们可称其为执行栈,或函数调用栈(Call Stack)。栈遵循”先进后出,后进先出”的规则来进行存放取出管理,也称LIFO (“Last In First Out”) 规则。通俗的来说,栈就是只有一个开口的容器,往里放东西和往外拿东西都是通过这个出口,最新放进去的东西在最底部,所以要最后一个拿出来,最后放进去的东西(最靠近容容器开口)反而在最上面,第一个拿出来。

执行栈(函数调用栈)的工作过程

有了以上的知识准备,我们来模拟下页面从打开到关闭的整个过程中,js如何使用执行栈来管理众多执行上下文的。

整个过程的就是三个操作的不断重复:入栈、执行、出栈入栈:程序执行进入一个执行环境时,它的执行上下文就会被创建,并被压入执行栈中;执行:被压入栈而处于”栈顶”的是当前正在执行函数的执行上下文,当函数调用完成后,它就会从栈顶被推出;出栈:程序执行完成时,它的执行上下文就会被销毁,并从栈顶被推出(出栈),控制权交由下一个执行上下文。js就是通过这种方式从而完成一段又一段代码和功能的。

第一步,进入全局上下文环境,全局执行上下文入栈,因为是第一个,所以在栈底部;

第二步,执行代码,调用函数funA,创建函数funA的执行上下文,并入栈;

第三步,执行函数funA内部代码,调用函数funB,创建函数funB的执行上下文,并入栈;

第四步,执行函数funB内部代码,该执行上下文结束,funB执行上下文出栈;

第五步,执行执行函数funA内部代码调用funB函数之后代码,funA执行上下文出栈;

此时页面未关闭,执行栈中仅留下全局执行上下文在栈底。

这就是整个执行上下文的管理过程。

需要特殊说明的是:因为JS执行中页面加载就会进入全局环境,所以处于”栈底的永远是全局环境的执行上下文”,这个全局会在页面关闭的时候被取出。在出栈的过程中,正常情况下当函数调用完成后,它就会从栈顶被推出,但是如果有闭包就会阻止该操作,也就是执行上下文不会被释放。

相比之前,是不是对闭包有了更深的理解呢!理解执行上下文,对于理解作用域以及作用域链都有很大的帮助。前端虽广,保持学习,终将拿下!

汇总:提高网页加载速度的优化思路与技巧

网页的加载速度直接影响到用户的体验,尤其是对于新用户,这种影响非常明显。但是网页的加载速度受到网络,服务器和浏览器等多方面的影响,所以网页优化不仅仅涉及后端开发,前端同样“大有可为”,所以本文在前端的角度来探讨网页优化思路和技巧。

网页加载慢的后果

1 严重影响直接用户体验

这个每个人都有很直观的体会,如果一个网页半天打不开,绝大部分人都会选择直接关闭。也许数字更具说服力:有统计表明,如果页面超过3秒没有加载出来,那么至少会白白丢失40%以上的用户,所以给出的建议让用户能够等待的时间不要超过2秒。

2 影响在SEO上的表现

搜索引擎爬虫总是尽量模仿人的行为从而提高其自身的服务水平,如果一个网页加载很慢,就会影响到网页在SEO上的排名,这回简洁影响你网页的曝光量。

网页加载的过程

一个完整的页面加载过程,包括客户端(浏览器)发送请求、网络传输、服务器接收请求处理并返回数据、数据经过网络传输、客户端接收数据并渲染呈现。所以网页的加载速度受到网络,服务器性能,客户端性能等多方面的影响,所以网页加载速度的提升,不仅仅是后端服务器的事情,前端仍然大有可为。

前端能做什么

在提高网页加载方面,前端主要要从以下几个方面入手:减少代码大小、优化代码结构、优化网络请求、页面异步延迟加载。

减少代码大小

包括html、css、js代码文件的大小,又包括两个方面:

第一是精简代码,提升性能。在确保功能和性能等软性要求的前提下,采用更简洁的代码实现方式,也就是说能用一行代码搞定的就不用两行,包括不必要的html标签嵌套,css代码、js代码,这对前端编程的要求较高,另外js局部变量的查找速度比全局变量快的多,所以js中尽量使用局部变量而非全局变量。第二个是优化代码。在编码过程中,为提高易读性,必然带有许多空格,注释,或者其他冗余的代码,使用代码压缩工具,快速删掉逗号、注释甚至不需要的空格,可以显著压缩 JavaScript 代码大小,这类的工具有Google Closure Compiler 等。不仅仅是js代码可以用工具压缩,css和html代码同样可以压缩,工具也有很多。除了本身代码的优化,在使用第三方库的时候,要记得删除一些不必要的组件。第三是优化图片资源图片本身比文字(代码)占有更大体积,但是一图胜千文,图片的使用可以极大的美化页面,所以图片还是要用的,但是在保证美观的前提下,我们可以对图片进行优化,寻求美观与速度之间的平衡。

第一就是图片压缩,减小图片本身的大小。压缩分为有损压缩和无损压缩,各有利弊,这里不再细说了。具体方法可以下载专门的工具,也可以在线压缩。

第二是如果可以用CSS样式来替代图片,那就尽量用CSS替代图片。现在随着CSS3的成熟,一些特殊的形状和效果都可以实现了,但这各很考验CSS的功底哈。

第三是使用图片的时候的小细节,能用background使用图片就尽量不用img标签来加载图片,另外如无必要,能使用png8就不使用gif格式。

第四就是图片预加载和延迟加载技术,比如使用缩略图,默认占位符替代图片,但不影响后续页面内容的呈现。

第五是有些情况下,可以将图片转base64,将图片变成字符串,这样可以减少http请求,但是这个仅适用一些体积较小的图片,比如图标,不然转换后的字符串会比较大,得不偿失。

优化代码结构

页面是按照从上到下的顺序加载页面内容的,首先 <head> 部分中的所有内容都会在在你看到页面内容(body标签里的内容)之前加载完成。js的加载会中断所有其他元素的加载,正常情况下,只有在js加载完成之后,才会继续加载后续css和DOM元素,所以在 <head> 部分中使用 JavaScript 会导致延迟页面内容的呈现。

对此有两个方法。1 将样式表放在头部,如非必要,则将脚本放在底部。2 如果必须放在head标签里,可以采用async 标记,这样在加载js的过程中,不会中断后续CSS和DOM的加载,以及其他JS的运行。

优化网络方面请求

1 减少网络请求次数

每次加载页面的时候,除了嵌入页面本身的css,js代码,其他独立的每个css、js文件、图片文件都会产生一个网络请求。可以通过对多个CSS文件,js文件的合并来减少文件的个数。对于图片则可以使用雪碧图的形式,将

2 减少域名DNS查找

DNS查找是网络请求的第一步,浏览器只有根据域名找到对应的IP地址才能找到目标服务器,这个查找IP的过程如果耗时比较久,就会影响到网页的请求速度,所以需要适当降低DNS的时间设置。

3 Cookie的管理,使用缓存,设置期限

在Cookie的管理上, 减小Cookie大小,设置合理的过期时间能可以有效的提高网页内容加载速度。

4 使用CDN服务也是选项之一。

曲线救国——延迟加载

通常情况下,需要等网页的所有内容加载出来才会呈现,但是如果页面的内容确实较多,比如一些综合性的门户网站,那用户等候的时间肯定就得非常长了。而实际上,用户最先看多的只有一个屏幕那么多的内容,所以只需要在最短的时间内,将第一屏或者前几屏的内容呈现出来就可以了,其他的内容可以在用户浏览第一屏内容的时候继续加载,类似上面提高的图片延迟加载的方法,页面异步延迟加载,可以让别人“感觉起来”加载速度很快,从而提高用户体验。

以上是一些提高网页加载速度方面思路和技巧的总结,水平有限,如果有遗漏或者更好更新的方法,欢迎指正与交流!

2019年前端开发又饱和了?是你能力不饱和吧

又是一年求职招聘季节,每每到这个时候,市场上就会出现一种声音:前端市场饱和了,工作难找了,尤其是最近互联网寒冬的阴影下,这种声音极具传播力。

但实际情况真的如此吗?

1 前端这几年的火热确实吸引了一大批人的注意力。从js登上最后欢迎编程版第一宝座就可以看出,前端目前有多火,而nodejs的横空出世让js有一统天下的迹象。前端的发展甚至吸引了大量其他行业的人员转行。

2 后端的程序员转向前端。这绝对是一个倒转的现象,曾经被后端不屑一顾的前端(也就是印象中的切页面人员)居然吸引了后端的人员,虽然这并不是一个很普遍的现象,但是至少后端工程师再也不敢小瞧前端了。后端转前端虽然不能说是易如反掌,但在前端工程化的趋势中,后端的经验也会起到非常大的作用,这种“降维打击”也给前端工程师增加不少的压力。

以上两种现象真实存在,客观上增加了供方市场的体量,也造成了前端人满为患,供大于求的假象。之所以说是假象,是因为现在的前端和切图时代的前端已不可同日而语,对前端的要求也提高了不少。而实际情况是很多人经过简单的学习,或者短期的培训,就以为掌握了前端的技能,可以完全胜任前端的工作,事实并非如此。对于已经有一些工作经验的前端工程师来说,前端的飞速发展也让他们难以跟上节奏,他们跳槽求职的时候,也很难和现在的市场需求橡匹配。

换句话说就是:前端仍然持续火热,需求旺盛,并不是供方有能力的人饱和了,供方饱和的永远是哪些能力不饱和的人,而有真正能力的人才一直是稀缺的。

客观来说,相对于其他高级程序语言,前端入门还是比较容易,但是前端要学精也不是容易的事情,所以不管是小白还是入行多年的前端开发者,在工作一段时间后,受限于学习动力以及公司项目的环境,前端的知识学习很容易遇到瓶颈,停滞不前。但前端的特点是庞杂、变化快,分散,所以要想跟上时代步伐, 不能求全责备,必须先理清思路,分清主次,抓住重点,才能将有限的精力发挥最大的作用,不断提升自己的前端开发能力。以下几点建议希望对大家这个认识有所帮助:

对理解的深度就是你薪水的高度

很多人疲于上手层出不穷的框架,却对基础知识一知半解,最终也只能对各个框架蜻蜓点水式的掌握,根本掌握不了精髓,还浪费了时间,这无异于舍本逐末。

1重视基础知识的深刻理解

很多人急于上手各种框架或者工具,以求最快的实现效果,完成任务。这个在实际项目中无可厚非,但是如事后不及时补充基础知识的短板,后期将会走得很艰难。不要对基础知识不屑一顾,css和js基础知识不仅要扎实,而且要深刻。

可以说,你对知识理解的深度决定了你水平和相应的薪水。以原生js为例,同样对闭包的理解:A说就是函数外部可以访问函数内部变量,而且可以举出例子;B说是作用域和原型链;C说是js的垃圾回收机制。那么现实是ABC就是对应三个工资等级。

其实对原生js的学习远不只这些,CSS的坑也不少,这些都是基础,但是你看的浅,别人看的深,就能走得更远。

2 学习框架,不依赖于框架

不仅要做搬运工,更要做制砖人。前端框架层出不穷,vue、react、Angular基本三足鼎立,其他框架不断涌现。工程师可以借助这些框架快速交付应用,但如果太依赖于框架,一旦框架一朝失宠,那么你也将被打入冷宫。所以对于框架,我们不能搬砖盖楼的人,更要以你自己去开放框架的角色去研究框架实现的原理和方法,自己学会造砖。

能带来效益的技术才是好的技术

只有创造商业价值,技术才有价值,毕竟公司赚到钱了,工程师才有工资发啊。但是商业价值的实现不是前端一个职位能创造的,而是和产品、后端、市场、运营等多部门的配合才能实现。所以你的前端开发不仅要从技术的角度上要求要更好的实现,更要从全局的视野来审视自己的开发工作,补充其他领域与前端相关的知识。比如你写的代码是否有利于SEO,是否适应用户的使用场景,你的交互是否能给用户带来的很好的体验,这些看起来似乎超越了你作为前端技术人员的要求,但这也是工作经验的体现,也是你区别于他人的地方。

良好的专业素养就是你的品牌

技术方面,代码规范:css规范,js规范,良好的注释习惯,这些看起来是个人的习惯,其实是你专业素养的体现。项目的完成是通过团队来完成的,如果你的个人习惯不尊崇一定的规范,无疑增加了你团队的沟通交流成本,那么老板就会从你的工资里将这部分成本拿回来。综合能力方便面,你的团队协作能力和沟通交流能力,相互学习能力,都将是影响到你作为一名前端工程师工作的重要方面,也是你个人品牌的重要组成部分。

前端一定要在最前端

前端变化快,那真是叫一个应接不暇。框架的版本迭代就不说了,vue你正准备入门,vue3.0已经发布了。es5还在学习中,es6还没整明白,但是es10就呼之欲出了。你以为h5风头正盛,但是h6已经近在眼前了,还有CSS4有多酷炫你了解过了吗?不仅传统的三大件在快速翻新,传统前端的之外的世界也同样精彩:TypeScript眼瞅就要赶超js了,据说今年不会ts都不好意说自己是前端。微信小程序稳健发展,阿里早早就跟上了,现在百度也有自己的小程序了,你还不会?

为啥要懂这么多,因为你是前端啊,你一定要在最前端啊!

好了,今天的大白话比较多,总结起来就是基础要深要稳(前端根基),做好自己(个人品牌),实现价值(能挣钱),持续学习。

立即执行函数的原理以及有哪些写法?

上一节的立即执行函数中,其实这只是立即调执行函数的一种方法,其本质是函数表达式后面接函数执行符号,让函数立即执行。下面就从上一篇文末的两个例子来探索和总结立即执行函数的原理和写法。

两个例子背后的误会

先来看看上一篇文末两个例子的面试题结果:

从打印结果可以看出第一个没有报错。

但是我们要注意的是,第一个看起来很像是要想立即执行函数(可能真实意图也是这样),并且没有报错,但是并不表示这个立即执行函数成功执行了,其实这个跟立即执行函数没有半点关系。另外从输出结果可以佐证,它并没有输出“立即执行1”,而是输出了6,这是为什么呢?

其实作者的本意非常的单纯,首先代码中(6)前面的代码就是一个标准的函数声明,然后就是想看看在函数声明的后面加上括号,整个函数会不会立即执行,结果却是一厢情愿。test函数没有执行,也就说明这个函数没有立即执行,这种让函数声明的方式是错误的。但为啥没有报错呢,因为这是一个美丽的误会。

回到刚刚,前面的函数声明没有问题,后面跟着的(6); 其实就是个表达式,只不过“灵活自由”的js没有强制每条语句后一定要增加分号隔开,所以写在一起也没有报错,说白了,上面这个例子在js引擎看来就是一个函数声明和一个表达式,和下图的写法是一个意思。

稍微改变一下例子就可以看出来,将后面括号表达式的6去掉后,就不再是表达式了,结果是语法错误,说明引擎没有把括号解释成你想要的立即执行的效果:

现在看第二个例子就很简单了。它也是想立即执行匿名函数,但其实本质上也是一个函数声明和一个表达式,但是很明显前面的这种函数声明是错误的,所以报错了,因为函数声明必须要函数名啊。而且即使加上了函数名,前面的函数声明没有错,后面直接跟上括号也会报错,因为这个括号加和前面的函数声明联系不上,而括号内是空的,又不是表达式,所以这对空括号完全的成了空运算符,所以报错了。

小结:两个例子就是一厢情愿加js的自由(不规范)造成的美丽误会。

你问我js的坑有多深,我说太平洋都很伤心。

一厢情愿产生的根源

为什么会产生在函数声明后加括号来达到立即执行函数效果的“想法”呢?先来回顾下上一篇立即执行函数以及普通函数的写法:

1 双括号形式:(function(){语句})();

2 三括号形式:((function(){语句})());

3 普通的函数声明和调用方式

以上可以看出,普通的函数调用就是在函数名加括号运算符,立即指向函数也是在后面加括号,只不过是在函数体后加。所以看起来似乎只要在想要执行的函数后面加括号就能起到让函数立即执行的效果。

这就是一厢情愿的根源所在。上面两个例子马上打脸了。

其实立即执行函数的本质是立即执行函数表达式。要想立即执行函数,则函数体必须是函数表达式,不能是函数声明,并且表达式后面要有括号运算符,表示调用函数。

立即执行函数有哪些写法?

基于以上讨论,立即执行函数的写法也就是将函数体转化为表达式的方法,主要有以下几种。以上面的第一个例子为例进行改造。

1 用括号运算符将函数体变化表达式,后面跟括号运算符立即执行

这得到了平常比较常用的写法。

2 同样用括号运算符,将整个函数声明和括号运算符变成表达式

这就得到了W3C推荐的写法。

3 其他操作符转化表达式,起到立即执行的效果

除了上面使用到的括号之外,还有!,+,-,=, && ,|| 以及逗号(,)操作符都可以讲函数体变成函数表达式。

赋值运算符:

其他操作符:

甚至用New方法

以上是立即执行函数的一些介绍。立即执行函数的优势是可以隔离作用域,为了避免变量污染。

求甚解:为什么控制台console打印输出的结果后面总是会多一个undefined?

立即执行函数是什么,与普通函数的区别在哪里?

函数是程序中非常重要的一部分,可以说没有函数就没有编程。

页面中JS执行顺序

JS是单线程的,执行顺序是按照文本顺序自上而下执行的,具体来说是按照js代码块(每一段script标签内的js代码)一段一段来执行的,而先执行的代码块不会被释放。

在每段js代码块中,js虽然没有像Java高级语言的预编译机制,但是在javascript中,会提前对函数声明优先解读,这就是js中的“函数式声明提升”,所以函数声明也可以在函数调用表达式后面。

也就是平常的函数调用,一旦声明,在这个页面的头尾随时都可以调用,函数声明一直存在着随时准备着被调用,没有被释放。

但是要注意:前面的代码块中不能调用在后面代码块的函数声明。

什么叫立即执行函数呢?

前面的函数都是声明和调用分开,而且可以不限次被调用,这是模块化思想的体现。但是对那些只需要执行一次的函数,怎么办呢?这就要用到立即执行函数。

立即执行函数,其实也可以叫初始化函数,英文名:IIFE,immediately-inovked-function expression。立即执行函数就是在定义的时候就立即执行,执行完以后就释放,包括函数内部的所有变量。

比如在页面完成初始化完成后执行的函数一般都是立即执行函数。

立即函数的表达式写法:

1 常用写法,也就是双括号形式:

2 w3c建议方式,也就是单括号的形式或者三括号形式:

其实就是讲方法1又装进了一个括号里面。

立即执行函数的使用须知

1 可以带参数

同其他正常的函数一样,立即执行函数可以带参数。

2 可以有返回值

立即执行函数可以有返回值,但是因为其执行完就释放了,所以要将其返回结果存放在一个变量中。

3 执行完立即销毁

报错,add函数未定义,因为立即执行函数执行之后就销毁了

立即执行函数的特别说明

立即执行函数不需要带函数名,如果带了函数名,则被自动忽略。

立即执行函数本篇先介绍到这,下一篇深挖以下立即执行函数的本质是什么。下面用两个题目来巩固下基础知识。

像数组又不是数组:类数组到底是什么?

在之前介绍模拟函数重载效果的时候,我们看到JS中的函数中,其实有一个“隐形”的东西来存储实参所有的实参,这个东西就是arguments对象。但在实际使用过程中它的使用方式和数组简直一模一样,用起来感觉就是数组,没有任何毛病,但实际上它并不是一个数组。

arguments看起来,用起来都像是数组

1 调用方法类似:都可以通过中括号下标的形式来访问具体某个参数。

2 有length属性:直接可以获取实参的个数。

3 和数组一样,用中括号将内容包含起来。

究竟是不是数组?

结合我们之前所学关于数组的知识点,刚好把它们串起来,从三个角度来验证下arguments是不是数组。

1 如果是数组,就可以使用前面介绍过的数组的方法,比如用push函数给arguments插入一个元素。

结果报错,arguments并不像正常的数组一样,有push函数

2 正面确认下,用我们之前判断数组的方法来判断一下:

结果为false,直接说明arguments不是数组。

3 这一切从控制台看的更为真切:

通过toString函数执行结果可以看出,arguments其实是“[object Arguments]”,其实将其直接打印出来的__proto__属性值就可以看出,arguments是Object。但是,这个arguments对象又跟我们看到的普通(正常)对象又有区别,因为一般的对象是大括号,而arguments对象是中括号,这一点又像数组。

综上,arguments本质上是对象,而且是具有数组特性的对象,包括表现形式都用中括号,这种就是艰巨对象和数组特性的数据类型就叫做类数组

横空出世:类数组

为了更深入的理解类数组,我们一步步来构造一个类数组。。

先从一个普通对象car开始,特别之处在于增加了数字作为属性,以及增加一个length属性,这样,我们就基本可以模拟出数组的使用方式了,并且仍然保持了对象的特性,如下图

但这个时候,虽然有点像了,但还有两点没有做到,一就是这个时候仍然不能使用数组的方法,二就是该对象打印出来还是使用大括号,也就是仍然只是一个对象。下面给这个对象添加数组的方法试试,以push和splice连个函数为例。

可以看到,在Object对象原型上增加数组的push和splice方法后,该对象之前的大括号也变成了数组的中括号,下面试试新增加的方法是否有效


可以看到,push方法成功将新的元素添加到对象中,类数组car就创建完成了。

需要特别说明的是:

1 数组的方法需要挨个添加到类数组中,需要哪个就添加哪个,并不是一次吧数组的所有方法一次性搬过来了

2 在对象中添加数组的位置,除了刚刚的在对象原型上添加,还可以在类数组中单独添加,这样只对本类数组有效,刚刚的方法会导致之后所有的对象都自带添加的数组方法。

之后创建的对象都自带数组操作

在类数组对象中添加数组的操作方法,只在本对象有效,不影响其他对象。

过程有点复杂,但是也正是通过这些,让我们透过表面的数据类型,有机会窥探原型,数组和对象的背后,看到 一些近乎颠覆三观的知识。类数组是一个难点,但是因为它兼具对象和数组的特性,非方便,所以经常要使用到,所以必须理解准确到位,使用熟练。

如果有哪里讲的不对或者需要补充,欢迎评论交流。最后来个很奇葩的大厂笔试题,以上知识学懂没学懂,欢迎大家评论区提交答案试试。

JS事件冒泡到底是啥?阻止冒泡的三种方法

JS事件冒泡是理解DOM操作的很好的例子,本文从事件、事件冒泡原理、冒泡处理三个角度来全面介绍JS的冒泡事件,你会发并没有那么神秘,跟那个冒泡排序也没有任何关系。

JS中的事件

要理解事件冒泡机制,就得先了解事件。

浏览器是事件驱动型的,根据用户的行为触发不同的事件,根据事件执行相应的操作。我们较为熟悉的事件有三大类型:鼠标键盘事件、页面事件、表单相关事件。

鼠标键盘事件:onclick、ondbclick、onmousedown、onmouseup、onmouseover、onmousemove、onmouseout、onkeypress、onkeydown、onkeyup;

页面事件:onload、onunload、onresize、onerror、onabort;

表单相关事件:onblur、onchange、onfocus、onreset、onsubmit。

需要注意的是事件处理程序中的变量event保留着事件对象的信息,包括比如click事件,事件属性里有点击位置相对于浏览器,以及页面的坐标信息,事件的类型(click),触发事件的DOM节点信息等。

什么是事件冒泡?

DOM中,树状结构决定了子元素肯定在父元素里,所以点击子元素,就同时点击了子元素和父元素,以及父元素的父元素,以此类推,当然最终的根节点都是文档,以及window。

试想,当一个子元素被点击的时候,不仅仅这个元素本身被点击了,因为这个元素也在其上一级父元素中(属于父级元素的地盘),所以相当于其父元素也被点击了,以此类推,一层一层往外推,最终整个文档也是被点击了,如果每个层级的节点元素都绑定了click事件,那么每个节点的click事件函数都会被执行。举个形象的例子,一个村里的人被打了(click),首先就要按照村里的规矩处理,同时这个村属于某个乡镇,当然也是相当于这个乡镇的人被打了,那么也要按照这个乡镇的规矩处理,以此一层一层往上报。这个例子不准确的地方就是,现实中一个人因为一个事件只会被处理一次,不会因为同一件事情多次处理。

以上这个事件从触发节点开始,逐层向上级元素传递的过程,就被形象地称为事件冒泡。以下例子很清晰展示了这个过程。

HTML代码

点击页面中 Child Span一次

控制台输出结果

从控制台的输出结果可以看到,虽然是Child Span被点击,而且是点击了一次,但是所有上层的父级元素的click事件都被触发,并且执行操作。

冒泡带来的烦恼

当上层(以及上上层,直至body元素)父级有子元素同样的方法,但你子元素的事件后,所有父级元素的同名函数也会从下到上,由里往外,挨个执行,但是大多数情况下,我们只希望子当事元素事件执行,不希望层层执行,这就要想办法阻止这种冒泡的情况发生。比如我们点击Child Span的时候只显示 Child Span的内容。结合刚刚的例子就是,村里发生了打人事件,在村里解决了,就没必要一层一层往上报,在层层处理了。

如何处理事件冒泡?

有三种思路可以处理事件冒泡带来的弊端:

1 阻止事件继续往上层传递的过程

利用事件的stopPropagation()函数终止往父级元素冒泡的过程。

再次点击span元素,其父元素没有因为span元素被点击而执行了。

注意 该方法具有兼容性的问题,Event.stopPropagation()在支持W3C的浏览器中使用没有问题,但是在IE浏览器就失效了,在IE中使用 Event.cancelBubble=true来代替:

if (Event.stopPropagation){

Event.stopPropagation(); }else{

Event.cancelBubble=true;

}

2 不阻止冒泡过程,修改执行的函数方法

这种方法是不阻止事件的冒泡过程,但是利用触发事件自带的信息,在各个元素节点的执行函数中添加判断,如果是当前节点触发的就执行,如果不是当前节点就不作任何操作。

这两种方法虽然在阻止事件冒泡方法不同,但实现起来都必须要在参与冒泡的过程所有节点元素挨个添加阻止函数或者判断函数,当节点表多的时候,这个操作起来就很会增加工作量,并造成大量重复代码,效率较低。

3 不阻止冒泡过程,在冒泡到根节点进行判断

这种方法同样不阻止事件的冒泡过程,同时也不在冒泡过程的各元素执行方法上添加判断,而是在冒泡过程中的某个父节点,对其所有下属的节点事件进行判断,然后执行相应的操作。

注意这个冒泡过程的最终节点,不一定要到body,到文档,到window,可以是冒泡过程中的任一一个终止冒泡的节点,所以我们可以在这个节点上,对其所有子元素的冒泡事件进行判断和处理。

比如我们选择DIV1作为统一处理节点,那么DIV1下的所有元素冒泡事件都在这里处理,而所有子元素都不做任何操作:

当然,这里为了说明问题,选择了DIV1,其实一般是在body节点进行。

其实这种元素本身触发事件,但是事件执行的方法不在元素本身,而是在其父元素的某个节点上,这种“偷懒”的模式,叫做事件委托。

以上就是事件冒泡的全部知识,如有错误欢迎指正,如有更好的讲解,也请留言交流。

Javascript数组都有哪些操作?懂这些玩的贼溜

在js中,数组是一种引用类型,是一种特殊的数据类型。数组就是一组数据的集合。

js中数组用起来相当方(随)便:定义时无需指定数据类型,可以无需指定数组长度,元素可以可以存储任何数据类型的数据。

数组的声明

我们知道,数组的声明有三种方式:

1 字面量方式

var arr1 = [1,2,3];

2 通过js内置的Array构造函数声明数组

var arr2 = new Array();

3 直接用构造函数生成数组,但是这种方法不直观,也基本不使用

var arr3 = Array();

数组到底是什么?

以上可以看出,数组的底层机制跟对象很类似,其实最终还是通过对象的键值对的形式来调用数据,数组的其实就是对象,是一种特殊形式的对象在,这也为啥说js中一切都是对象。

判断是不是数组的三种方法

在实际项目中,经常需要用到数组来传递数据,但是传递数据的方式不只有数组一种,所以在对数据进行处理之前,首先要判断数据的类型,才能采取对应的操作。下面介绍几种常用判断数组的方法:

var a =[];

1 通过其构造函数

2 通过instanceof 函数

需要注意的是,不建议使用instanceof在实际项目中使用。

3 利用原型toString函数

利用原型中的toString函数,如果返回值是”[object Array]”,则是数组。

在实际项目中,可以将toString 函数和参照物进行缓存,请看以下例子:

实际项目中,推荐使用原型toString函数来判断是不是数组。

以上是介绍数组的基本情况,接下一篇整理所有关于数组的操作,争取比较彻底和深入的理解数组。

如有错误和其他补充,欢迎评论留言,只为相互提高!