阅读前言
转眼间9月的到来,十分感慨这时光的飞逝。9月对程序员有着十分重要的意义,想在这个优胜劣汰的代码世界活下去,金3银4,金9银10,都属于选择的机会。
在这优势略汰且经济回陇的状态下,笔者身处央企都无法安稳,如今也要考虑工作的问题(有广州 内推 ,欢迎联系),我们可以选择的只能是,逐步的打好自己的基础,才能在不安稳的社会形态下,逆行保持自己的安稳。笔者也该复习复习,在此汇总一下自己过去所学过的基础。
本文的难度级别,个人定位为中级前端开发工程师。且自负的认为前端重要基础的均汇总,如有遗漏,希望你评论喷我,喷我如果能学习到知识,愿听闻。笔者也是逐步前进的小伙,当前也需要及时的补充自己,过程如有不对的地方,尽快指出。如文章对你有帮助,希望能给于鼓励,手动点个赞吧。
汇总内容包含看过的书籍,自己对程序的理解,以及部分大神的借鉴(很多都是曾经记下的知识点,来源我也忘记是从哪里,如没有提及希望包涵)。
但文章全文,均为笔者一字一字手敲。写给自己供于复习,同时分享给在前端一起努力朋友。
一.前端基础
前端基础,个人认为就是html + js + css。无论过程如何,无论你用的是less还是sass,无论你用的vue还是react,输出的结果,只有html + js + css。
此部分列举,笔者觉得重点的知识点,如有遗漏,欢迎指出。
1)html篇
html章节,本文仅列出笔者任务相对重要的知识点,且介绍上,针对重点。当然,遗漏很正常,希望能收到你的意见。
1.语义化
所谓,语义化的标签,说明让标签有自己的含义。也是近十年。最典型的栗子就是header,footer等,它可以让你在没有样式的情况下,就大概能想到,他就是个头部或者底部。他存在的意义,就是让前端开发人员,在开发过程中,更容易去阅读代码,以及明白这些代码的意义。
它的好处是: 1.能够更好的展示内容结构 2.便于团队的维护与开发 3.有利于SEO,爬虫可以分析每个关键词的权重。 4.方便其他设备解析 (如屏幕阅读器)
2.SEO
作为前端,你不得不知道的SEO,这涉及到公司的网站推广。
SEO,中文称搜索引擎优化,一种利用搜索引擎的搜索规则来提高目前网站在有关搜索引擎内的自然排名的方式。他的实现原来分别为,页面抓取,分析入库,检索排序。
3.doctype
前端经常在html头部看到DOCTYPE的声明,一般常位于文档的第一行。那么他的作用是什么,可能对新的浏览器或者新的网站暂无什么影响,但是相对古老的浏览器或者是网站,可能会出现不同。因为浏览器有标准模式与兼容模式,差异相对比较大。
标准模式的渲染方式和 JS 引擎的解析方式都是以该浏览器支持的最高标准运行。 兼容模式中,页面以宽松的向后兼容的方式显示 ,模拟老式浏览器的行为以防止站点无法工作。
而DOCTYPE的存在,就是为了声明,该页面使用标准模式。不声明,可能一些旧的网站会出现兼容模式。
4.link与@import
link与import , 本质使用上,我们都是用他来引入css,但是他们有一定的区别。
- link是一种引入资源的标签,import是引入css的方式。所以,import引入的只能是css,而link可以引入所有的资源,包括图片,RSS等。
- 加载顺序上也有一些差异。 link引用的CSS会同时被加载。 import引用的CSS会等到页面全部被下载完再加载。
- 兼容性的差别。 link无任何兼容问题,import兼容IE5以上。(当然,IE5估计也找不到了)
- 动态引入样式 link可以后期引入样式,而import是不可以后期引入的,只能初始化页面之前引入。
- 复用率的问题 import可以复用之前的css文件,而link只能一次引用一个文件。 当然,import复用文件时,在浏览器实际上是加载了多个文件,会有多个请求。而每一个link只是一个http请求。
5.async与defer
首先这两个东西为什么而存在的问题。 在日渐复杂的前端,异常已经是程序的一部分。如果出现一些小问题,或者服务器加载上出现延迟。而我们默认的引入的script脚本,会阻塞后续的DOM渲染。一旦没有部分异常无法及时加载完成,那么我们的页面因为阻塞问题,将整个白屏。
也许我们可以保证自己服务器的正常,但是你决定保证不了第三方服务器的正常,于是引入了async和defer来优化这个问题。
再来谈谈script的默认,async,defer的之前的差异。
默认情况下: 浏览器会立即加载并执行指定的脚本。指定的脚本,指在script标签之上的脚本。所以,如果script放在header中,而对应的文件还未加载完成,会形成阻塞。所以这就是现在很多页面,都会使用默认且把scipt放在页面结尾的原因。
async情况下: async ,加载和渲染后续文档元素的过程将和 script.js 的加载与执行并行进行(异步)。async是乱序的。
defer情况下: defer,加载后续文档元素的过程将和 script.js 的加载并行进行(异步),但是 script.js 的执行要在所有元素解析完成之后,DOMContentLoaded 事件触发之前完成。defer是顺序执行。
此外,async跟defer,不支持或者不兼容IE9一下浏览器,总体来说,笔者还是觉得script放最下方靠谱一些。
6.文本元素的冒泡与委托
适合用事件委托的事件:click,mousedown,mouseup,keydown,keyup,keypress。
- 优点: 1.减少事件注册,节省内存。例如上面代码,只指定 父元素的处理程序,即可管理所有所有子元素的“click”事件; 2.简化了dom节点更新时,相应事件的更新
- 缺点: 1.利用事件冒泡的原理,不支持不冒泡的事件; 2.层级过多,冒泡过程中,可能会被某层阻止掉; 3. 理论上委托会导致浏览器频繁调用处理函数,虽然很可能不需要处理。所以建议就近委托,比如在ol上代理li,而不是在document上代理li。 4. 把所有事件都用代理就可能会出现事件误判。比如,在document中代理了所有button的click事件,另外的人在引用改js时,可能不知道,造成单击button触发了两个click事件。
2)css篇
css章节,本文仅列出笔者任务相对重要的知识点,且介绍上,针对重点。当然,遗漏很正常,希望能收到你的意见。
1.盒子模型
盒子模型,个人的理解,就是一个来装html标签的容器,包装的内容包括content+padding+border+margin。由这四个组成我们的"盒子"。
我们日常可能会遇到不同的浏览器,元素的高宽不一致。除了可能是浏览器内置的margin跟padding不同之外,也可能是IE跟w3c的盒子模型组成不同。
以下是两种不同盒子的分类:
- W3C盒子模型:可通过box-sizing: content-box来设置,他包含content+padding+border+margin。
- IE盒子模型:可通过box-sizing: border-box来设置,content+margin。其中content包含border,padding。
2.BFC
简单的个人理解,block formatting context,块级格式化上下文。产生了BFC的,形成了独立容器,他的特性就是不会再布局中影响到外边的元素。
他的特性:
- 1)BFC边距会重叠。
- 2)BFC的内外元素互相不影响
- 3)BFC不会与浮动元素发生重叠
- 4)BFC元素的高度计算会包括元素内的浮动元素的高度
触发的条件是:
- 1)body 根元素
- 2)浮动元素:float 除 none 以外的值
- 3)绝对定位元素:position (absolute、fixed)
- 4)display 为 inline-block、table-cells、flex,table-caption
- 5)overflow 除了 visible 以外的值 (hidden、auto、scroll)
此外,除了BFC,还有IFC、GFC、FFC的概念。我们简单了解一下。
- GFC:可简单理解为grid布局
- FFC:可简单理解为flex布局。
- IFC:内联格式化上下文,简单理解为:inline-block。
水平方向上的 margin,border 和 padding在框之间得到保留。框在垂直方向上可以以不同的方式对齐:它们的顶部或底部对齐,或根据其中文字的基线对齐。包含那些框的长方形区域,会形成一行,叫做行框。 inline-block的元素的内部是一个BFC,但是它本身可以和其它inline元素一起形成IFC。
3.flex布局
flex,即弹性布局。一个由css3引入,为我们的盒子属性带来灵活性的一种布局方式。一旦父级采用了flex布局,里边的子控件将收flex布局限制,部分原本的样式(如float:left)也会失效。
基本api不做讲解,不熟悉可以看看:www.ruanyifeng.com/blog/2015/0…
特别注意: flex:0 0 30%的意义: 等于flex-grow=0(默认不放大)+flex-shrink=0(不缩小)+flex-basis=30%( 项目占据主轴的空间)
4.css3新特性
- 背景,支持RGBA透明度,一次多背景图
- 支持媒体查询
- 支持阴影,渐变,阴影
- 支持边框图片,border-image: url(border.png) 30 30 round
- 支持transform位移系列
- 支持过渡效果transition
- 支持自定义字体
- 引入flex/grid布局
- 引入多种选择器
- 其他不做详细说明,有兴趣搜一下css3新特性
5.图片格式
前端的图片分类格式,其实是性能优化的很大部分。选择好图片的类型,对前端的性能影响非常大。
而前端对图片的精髓,一方面是对图片大小的评估,一方面是对图片的类型选择。
他的大小可以这样判断:
比如一张200*200的图片大小,这时候,他的像素点有40000个。每个像素有 4 个通道, 所以一共有160000个字节,所以,我们评估该图片的大小大概为:160000/1024 约等于 156(KB), 如果大很多,说明图片大小有优化控件。如果小很多,说明此时是模糊的。
图片类型 | 介绍 | 使用场景 |
png | 适合颜色简单,但是对图片质量比较高。日常用的png8,此外还有png32, | 适合logo体积太大一般不用 |
jpeg | 不影响图片质量的情况有损压缩,banner图。适合大图。 | 压缩后大小可省略很多,一般大图使用 |
svg | 对性能有损耗,体积小,压缩性抢。可在质量不下降的过程被放大 | 部分浏览器兼容性不太好 |
webp | 只针对谷歌,兼容性不好。图片大小能压缩30~40%。 | 谷歌浏览器用,如有非常注重性能的产品,可判断浏览器加载不同类型图片 |
base64 | 压缩成字符流,实际大小是变大了,但是好处就是减少了http请求 | 一般也针对小图标 |
6.移动端适配
列举一下笔者所知道的适配方式:
- 1)媒体查询。 该方案的话,个人觉得是最佳的方案,也是常用UI库非常喜欢的用处理方式之一。唯一不好的是:多套媒体方案,也意味多份的工作量。
- 2)vw/vh 利用单位vw/vh进行布局。该方案的话,对整体的布局还是相对稳定,但是对部分细节等处理还是不优化。且遇到手机屏幕差异较大的话,会出现严重的视差。
- 3)rem 相对稳定的方法。根据屏幕大小计算出font-size;但是只能求良好,很难求精。如果UI对一像素非常的敏感,这个方案可能是个非常糟糕的选择。
- 4)类似小程序rpx。 相信原生小程序开发者都用过rpx。这里其实原理有点类似rem。但是,却不是按屏幕大小去计算,而是不同的屏幕定义了自己的标准。
7.常见兼容性
这个问题本次只列举了几个常见的,非全部列出。如需具体,可另查资料。
1)间距差异是否大,导致文本换行,或者间隔太大。 原因:每个浏览器的margin和padding的默认值不同。 解决方案:全局文件设置统一默认margin和padding。
2)图片默认有间距 原因:因为img标签是行内属性标签,所以只要不超出容器宽度,img标签都会排在一行里,但是部分浏览器的img标签之间会有个间距。去掉这个间距使用float是正道。(我的一个学生使用负margin,虽然能解决,但负margin本身就是容易引起浏览器兼容问题的用法,所以我禁止他们使用) 解决方案:使用float属性为img布局
3)较小的高度(小于10px),时,ie可能会超出高度 原因:IE有一个默认的行高的高度 解决方案:给超出高度的标签设置overflow:hidden;或者设置行高line-height 小于你设置的高度。
为min-height本身就是一个不兼容的CSS属性
4)透明度兼容设置 原因:不同浏览器各自透明度关键字不统一。 解决方案:filter:alpha(opacity=50); -moz-opacity:0.5; -khtml-opacity: 0.5; opacity: 0.5;
5)IE的hover图片会闪烁 原因:IE6的每次触发 hover 的时候都会重新加载 解决方案:提前缓存文件。document.execCommand("BackgroundImageCache", false, true);
8.垂直居中
该回复只给与思路,没有具体写法。因为我觉得大家都应该懂。
已知宽高: 1.margin 自己算高宽 2.定位 + margin-top + margin-left 3.定位 + margin:auto
未知宽高: 1.transform 但有IE兼容的问题 2.flex 布局 3.display: table-cell
9.实现1px
首先你可能需要了解一下物理像素跟独立像素的区别。
物理像素: 一个物理像素是显示器(手机屏幕)上最小的物理显示单元,如:iPhone6上就有7501334个物理像素颗粒。 独立像素:逻辑像素,程序使用的虚拟像素。如:iPhone6上就有375677个独立像素。
那么如何实现1px呢: 1.利用 transfrom 的 scale 缩放来实现 2.利用 background 的 line-gradient 线性渐变来实现 3.meta viewport修改成1比0.5。这样整个屏幕的大小缩小了0.5。 4.利用box-shadow
10.三列布局
该回复只给思路
1.CSS浮动 第一个float:left,第二个float:right,第三个设置margin-left和margin-right
2.绝对定位法 第一个定位到left,第二个定位到right,第三个设置margin-left和margin-right
3.flex布局
11.样式优化
初步聊聊个人的样式优化方案如下:
1.避免css层级太深。有兴趣了解一下css tree如何跟html tree融合成dom tree。 2.首屏(特别是缓冲效果图)可适当使用内联元素。这样有利于更快的显示。 3.异步加载CSS。非首次重要引入的css文件,不放在head里边。这样会引起阻塞。 4.减少 回流 的属性。如display:none可以考虑使用visibility 5.适当使用GPU渲染。如tranfrom等。 6.css动画的性能,是远远的大于js动画性能。 7.利用工具压缩,去重。
12.伪类和伪元素
伪类和伪元素的根本区别在于:它们是否创造了新的元素
伪类,指可以通过元素选择器,就可以实现的效果,如frist-child,active等。 而伪元素,是指需要通过创元素,才可以实现的效果,如first-letter,before,after等。
3)javaScript篇
javaScript篇,由于扩展性十分全。对于大神来说,每一个点,都可以做一篇简介参考。 本文只能是概念上的简介,或者是个人对应的理解。如理解有误,欢迎吐槽。
1.内置对象
内置对象,也叫原始类型。
原始类型有5个,null,undefined,boolean,number,string。 es6引入了symbol,可以用来做独立标识用。 es10引入了bigint, 主要用的大数据。number最大值2的53次方,超过只能使用bigint。 截至目前为止,一共是7个。
原始类型存储的都是值,他的原型汇总,是没有任何函数的。如果你看到类型有函数,比如toString,那说明类型给转换为了对象类型,此时才有toString方法。
原始类型存储的是值,对象类型存储的是地址。
2.闭包
简单的理解是,一个绑定了执行环境的函数,可以访问到外部环境的变量。
他的好处就是: 变量常驻内存,对于实现某些业务很有帮助,比如计数器之类的。 架起了一座桥梁,让函数外部访问函数内部变量成为可能。 私有化,一定程序上解决命名冲突问题,可以实现私有变量。
缺陷是: 他的变量常驻在内存中,其占用内存无法被GC回收,导致内存溢出。
注意,闭包的原理是作用域链,所以闭包访问的上级作用域中的变量是个对象,其值为其运算结束后的最后一个值。
3.执行上下文
代码运行时,产生一个对应的执行环境,这个叫做执行上下文。
通常执行上下文,有三个环境:
1.全局环境:代码首先进入的环境
2.函数环境:函数被调用时执行的环境
3.eval函数
执行上下文,可分为三个阶段,分别为创建,执行,销毁阶段。我们简单的分析一下,各个阶段分别处理了什么。
- 创建阶段:
- (1).生成变量对象
- (2).建立作用域链
- (3).确定 this 指向
- 执行阶段:
- (1).变量赋值
- (2).函数引用
- (3).执行其他代码
- 销毁阶段: 执行完毕出栈,等待回收被销毁
4.原型/原型链
指构造函数的内置属性,即prototype属性。每个构造函数都自带prototype属性,指向一个对象,常用实例共享属性和方法的。
Prototype.constructor会指向原构造函数
对象的原型,也是个对象。只要对象的原型有值,不为null,他就还有原型。所以构成了原型链。
5.作用链域
作用域链的原理和原型链很类似,如果这个变量在自己的作用域中没有,那么它会寻找父级的,直到最顶层。 注意:JS没有块级作用域,若要形成块级作用域,可通过(function(){})();立即执行的形式实现。
6.继承
继承的几种方式:
1.原型链继承 本质是重写了对象。
缺点:
1)对象实例共享所有继承的属性和方法
2)不能传递参数
2.构造函数继承 在子类构造函数的内部调用超类型构造函数。使用aapply()和call() 方法
缺点:
1)函数复用性不高
2)只能继承实例上的属性,原型上的方法不可见
3.组合继承 本质:原型链 + 构造函数 Parent.call(this) new Parent()避免了上述的缺点,常用。
优点:可传参,不会与父类引用属性共享
缺点:继承父类函数的时候调用了父类构造函数,导致子类的原型上多了不需要的父类属性,存在内存上的浪费。
4.原型式继承 实现本质:object()函数对传入其中的对象执行了一次浅复制
5.寄生式继承 借用构造函数来继承属性,通过原型链的混成形式来继承方法
6.寄生组合 高效率只调用了一次构造函数,集寄生式继承和组合继承的优点于一身,是实现基于类型继承的最有效方式。 就是将父类的原型赋值给了子类,并且将构造函数设置为子类,这样既解决了无用的父类属性问题 Parent.call + Object.create()
7.class继承
7.this关键字
1) this总是指向函数的直接调用者(而非间接调用者) 2) 如果有new关键字,this指向new出来的那个对象 3) 在事件中,this指向目标元素,特殊的是IE的attachEvent中的this总是指向全局对象window。
this大概有以下五种场景: 1.绑定事件指向事件本身 2.普通函数的,指向方法体。 3.new函数的指向当前类 4.箭头函数,指向上级上下文 5.call/apply/bind
8.new关键字
看以下代码,这就是new的整体过程。
function createThis( proto ){
var obj = new Object;
obj.__proto__ = proto.prototype;
let [ constructor, ...args] = [ ...arguments ];
let result = constructor.apply( obj, args );
return typeof result === 'object' ? result : obj;
}复制代码
可以从代码中看到new的执行过程,新建一个对象,设置原型链,改变this指向,根据对象返回结果。
9.类型的判断
谈到js类型的判断,我们能想起 typeof,instanceof,constructor,Object.prototype.toString.call()。(没了吧?还有的话提醒我一下)
那么我们对比一下他们的作用与区别。
typeof 对于原始类型来说,除了 null 都可以显示正确的类型。但是对于对象来说,除了函数都会显示 object,所以他的作用,仅仅只能判断原始类型,判断不了对象。
instanceof,用于判断一个变量是否某个对象的实例,内部机制是通过原型链来判断的。他的确能判断是否类型的是否正确。但一点值得注意,instanceof 检测的是原型,原型链上,每一个类型,都会返回true。所以,只能用来判断两个对象是否属于实例关系, 而不能判断一个对象实例具体属于哪种类型。
constructor, 是原型prototype的一个属性,当函数被定义时候,js引擎会为函数添加原型prototype,并且这个prototype中constructor属性指向函数引用, 因此重写prototype会丢失原来的constructor。
但是他也有明显的缺陷:
1:null 和 undefined 无constructor,这种方法判断不了。 2:还有,如果自定义对象,开发者重写prototype之后,原有的constructor会丢失,因此,为了规范开发,在重写对象原型时一般都需要重新给 constructor 赋值,以保证对象实例的类型不被篡改。
toString是几个方案中,相对比较不错的方案。建议使用。toString() 是 Object 的原型方法,调用该方法,默认返回当前对象的 [[Class]] 。这是一个内部属性,其格式为 [object Xxx] ,其中 Xxx 就是对象的类型。
10.类型的转换
js类型的转换,可以分为三种情况:
- 转换为布尔值
- 转换为数字
- 转换为字符串
其中,转化为boolean,除了 undefined, null, false, NaN, '', 0, -0,其他所有值都转为 true。我们日常可以用它来判断对象是否未赋值。
11.比较运算符
比较运算符,是我们常用到的。如果都为number类型,比较值的大小,那么当然简单咯。如果是非number值的时候如何处理呢?
顺序如下: 将值转换为原始值(ToPrimitive方法) 转换为数字(valueOf方法) 转换为字符串(toString方法)
12.四则运算符
这里笔者的记忆是这样的,分为两类:
- 加法类: 只要有运算有字符串,那么将全部转为字符串。 如果不是字符串(且数字),那就把它转换为(字符串)或数字。
那么如何判断先转为数字还是转为字符串呢?这涉及到加法运算会触发三种类型转换。 参考“比较运算符”,ToPrimitive方法。
- 非加法类: 只要其中一方是数字,那么另一方就转为数字。
13.拷贝
拷贝,任何语言都有自己的深拷贝以及浅拷贝。深拷贝有利于数据的完全独立,但是全是深拷贝的话,内存又不会不断的往上涨,于是又有了浅拷贝。
- 浅拷贝指拷贝引用对象,仍指向同一个地址,修改时原对象也会受到影响.。
- 深拷贝完全拷贝一个新对象,修改时原对象不再受到任何影响
基于内存的节省,我们日常用到的函数,很多都属于浅拷贝,比如我们的扩展运算符,还有Object.assign,contact,slice等。都属于浅拷贝。
而深拷贝:
* 可以使用JSON.parse(JSON.stringify(obj))。性能最快。其弊端也必将明显,首先无法拷贝函数、undefined、或symbol等值。其二对象要是有自身循环调用,会报错。
* 利用递归来实现每一层都重新创建对象并赋值
* 如何用jquery,可以考虑,$.extend( [deep ], target, object1 [, objectN ] ),这也是深拷贝的一种。
* 也可以利用lodash.js,cloneDeep方法进行深拷贝。复制代码
14.函数调用
js的函数调用,有四种方式:
- 1.方法调用模式(this指向他本身)
- 2.函数调用模式(this指向windows)
- 3.构造器调用模式(利用原型构造,JS摒弃这个方法)
- 4.apply调用模式(利用apply改变this对象。)
函数调用,自身携带的,记住有 this 和 arguments
15.高阶函数
接收函数作为参数或者返回函数的函数,都可成为高阶函数。 所以常见的方法有:map,filter,bind,apply等。
需要了解一下,高阶函数实现AOP。
16.柯里化函数
柯里化,实现上,就是返回一个高阶函数,通过闭包把传入的参数保存起来。当传入的参数数量不足时,递归调用 bind 方法;数量足够时则立即执行函数。学习一下 javascript 的高阶用法还是有意义的。
柯里化是一种将使用多个参数的一个函数转换成一系列使用一个参数的函数的技术。
17.数组
数组的方法可以写的实在是太多了。 借助一下这位小伙伴的博客:juejin.im/post/684490…
18.伪数组
伪数组,说明它不是真正意义上的数组,他的输出是个对象,但他的原型并不指向Array。
常见的伪数组包括:arguments、getElementsByTagName等获取的NodeList对象
它的特性是:
- 1)具有length属性;
- 2)按索引方式存储数据;
- 3)没有内置方法,不具有数组的push()、pop()等方法
伪数组也可以转换为数组,可以通过:
- var args = Array.prototype.slice.call(arguments);
- Array.from(arguments)
- 扩展运算符
19.重定向this
call,apply,bind,三者都是用来改变函数的this对象的指向的。且第一个参数都是this要指向的对象,也就是想指定的上下文。
但传参的值也不同,apply后续只能传递数组,而call与bind可以传递多个参数。
bind 是返回对应函数,便于稍后调用;apply 、call 则是立即调用。
20.严格模式
use strict是否很熟悉?了解一下他的大概作用: 1) 消除js不合理,不严谨地方,减少怪异行为 2) 消除代码运行的不安全之处, 3) 提高编译器的效率,增加运行速度 4) 为未来的js新版本做铺垫。
21.for循环
首先效率问题: for > forEach > map
如何选择对应的循环呢:
- 如果需要将数组按照某种规则映射为另一个数组 map
- 如果需要进行简单的遍历 forEach 或者 for of
- 如果需要对迭代器进行遍历 for of
- 如果需要过滤出符合条件的项 filter
此外,我们要明白传统for
这个 for-of 循环首先调用了 values 数组的 Symbol.iterator 方法,获取了一个迭代器 (对 Symbol.iterator 的调用发生在 JS 引擎后台)。接下来 iterator.next() 被调用,迭 代器结果对象的 value 属性被读出并放入了第一个结果变量。 如果你只是简单地迭代数组或集合的值,那么使用 for-of 循环而不是 for 循环就是个好 主意。 for-of 循环一般不易出错,因为需要留意的条件更少;传统的 for 循环被保留用 于处理更复杂的控制条件。 在不可迭代对象、 null 或 undefined 上使用 for-of 语句,会抛出错误。
二.前端基础进阶
1)ES6篇
1.模块化
在以前,js一直没有模块化的体系。这就会产生一个问题,当项目到达大型时,很大可能性出现方法重叠,以及安全性问题,成为大型项目的一个痛点与障碍。而es6模块化正式为此诞生。
这里简述前端模块化的区别:
1)AMD, commonJS, 与es6,都属于预加载类型。而后期引入的CDM是懒加载。 何为预加载, 也就是说,在程序调用,所有的模块都加载完成。 而懒加载,是用到什么的时候,才去加载什么。
2)AMD跟cmd专注于前端的规范。而commonjs跟es6 moudle可用于前后端。
3)AMD的代表做为requirejs,cmd的代表作为seajs。commonjs 与 es6,则无需引入, 只需要引入编译器(如babel)即可。 seajs为淘宝引入的规范,我们都知道淘宝相对很大, 不采用懒加载,首屏的时间将会很长,不过现在已经停止维护。
4)es6 跟 commonJS做了如下改变:
1.ES6只能新增值,无法重新赋值就会报错
2.CommonJS 输出是值的拷贝,即原来模块中的值改变不会影响已经加载的该值, ES6静态分析,动态引用,输出的是值的引用,值改变,引用也改变,即原来模块中的值改变则该加载的值也改变。
3.CommonJS 模块是运行时加载,ES6 模块是编译时输出接口。
4.CommonJS 加载的是整个模块,即将所有的接口全部加载进来, ES6 可以单独加载其中的某个接口(方法)。
5.CommonJS this 指向当前模块,ES6 this指向undefined
2.变量声明
变量声明(var)会有变量提升。变量会提前初始化,也可以提前访问。当项目变量复杂的时候,很容易产生bug。es6就在这个时候,引入了let跟const。
当然,引入let与const不仅仅解决了变量提升的问题,他们的不同如下:
- 1)局部作用域 新引入的let,const声明,再不会再产生变量提升。避免了变量提前访问的场景,间接的提高了严谨性。我们可以在程序运行时就知道了报错,而非后期的调试中。
- 2)禁止重复声明 如果一个标识符已经在代码块内部被定义,那么在此代码块内使用同一个标识符进行 let 声明就会导致抛出错误
- 3)区分常量与变量 这是let与const的区别。const 声明会阻止对于变量绑定与变量自身值的修改,避免了我们日常开发中,了不小心改到常量的问题。
- 4)暂时性死区 下述案例,用let跟var定义的结果,就明白什么叫暂时性死区
for( let i = 0; i<10; i++ ){
setTimeOut( function(){
alert(i );
}, 1000);
}
3.Symbol
Symbol是JS新引入的基本类型。我们都知道在ES5之前,JS 已有的基本类型(字符串、数值、布尔类型、 null 与 undefined )之外, ES6 引入 了一种新的基本类型。
符号起初被设计用于创建对象私有成员,而这也 是 JS 开发者期待已久的特性。 在符号诞生之前,将字符串作为属性名称导致属性可以被轻易 访问,无论命名规则如何。 而“私有名称”意味着开发者可以创建非字符串类型的属性名称,由 此可以防止使用常规手段来探查这些名称。
我们常用于: 1.作为内置属性名称。可以避免同参数名的覆盖。 2.使用Symbol来替代常量。Symbol来创建一些常量。比如订单状态等,可以也可以避免重复。
4.数组的扩展
需要明白Array.of跟Array.form的意义。
首先上述提到,数组有了伪数组的概念,而转化为数组,可以通过 Array.prototype.slice.call(arguments)。但是这个方法并不直观,所以引入了更为直观的Array.form。
只要是部署了iterator(下边会提及)接口的数据结构,Array.from都能将其转为数组。
而Array.of是为了解决new Array()的严谨性的问题。 new Array( )后边的值,可能代表长度,可能代表数值。
Array.of基本上可以用来替代Array()或newArray(),并且不存在由于参数不同而导致的重载,而且他们的行为非常统一。
5.函数的扩展
es6对函数的扩展,主要针对两个,一个是箭头函数,一个是解构函数。
箭头函数跟普通函数的区别:
- (1)用了箭头函数,this就不是指向window,而是父级(指向是可变的)。
- (2)不能使用arguments对象。
- (3)不能用作构造函数,这就是说不能够使用new命令,否则会抛出一个错误。
- (4)不可以使用yield命令,因此箭头函数不能用作Generator函数
这里,简单提及解构函数,解构数组,以及字符串模版等概念。
6.Map,Set,WeakMap与WeakSet
数组在 JS 中的使用正如其他语言的数组一样,但缺少更多类型的集合导致数组也经常被当作队列与栈来使用。 数组只使用了数值型的索引,而如果非数值型的索引是必要的,开发者便会使用非数组的对 象。
Map Map与Object,其最本质的区别,键值对的集合(Hash 结构),但是传统上只能用字符串当作键。
对于Map来说,undefined和null是两个不同的键,布尔值true和字符串true是两个不同的键,而NaN之间视为同一个键 ,0和-0也是一个键,
const map = new Map();
map.set(['a'], 1);
map.get(['a']) 复制代码
会输出underfined。
WeakMap
WeakMap跟Map结构类似,也是用于生成键值对的集合,但是他只能用对象,来作为键值。其次,WeakMap的键名所指向的对象,不计入垃圾回收机制。
WeakMap 与 Map 在 API 上的区别主要是两个,一是没有遍历操作(即没有keys()、values()和entries()方法),也没有size属性。因为没有办法列出所有键名,某个键名是否存在完全不可预测,跟垃圾回收机制是否运行相关。这一刻可以取到键名,下一刻垃圾回收机制突然运行了,这个键名就没了,为了防止出现不确定性,就统一规定不能取到键名。二是无法清空,即不支持clear方法。因此,WeakMap只有四个方法可用:get()、set()、has()、delete()。
WeakMap的实例比较少,个人从来没有在实践中使用。但有这么一个实例相对适合:比如我们要统计一个页面统计该页面所有节点的点击次数。
- 其一,首先我们获取到的dom是一个对象,符合作为键值。
- 其二,当对应的节点消失的时候,垃圾回收机制,回自动回收对应的在WeakMap节点,同时达到释放内存的目的
Set Set可能相对更好理解,他可以简单理解为是一个“无重复值”的“有序”列表,且运行值方便快速访问以及判断。
我们可以利用他去重。包括数组,字符串等。
也可以利用他去接受一些具有 iterable 接口的其他数据结构,例如我们统计页面有几个div? new Set(document.querySelectorAll('div'));
WeakSet 跟WeakMap类似,还是两个关键字:“对象”,“内存”。
7.iterator
迭代器iterator, 可以理解成一个为不同的数据结构,统一访问的机制(Symbol.iterator属性)。只要对应的数据结构有Symbol.iterator属性,就可以完成遍历操作。
function createIterator(items) {
var i = 0;
return {
next: function() {
var done = (i >= items.length);
var value = !done ? items[i++] : undefined;
return {
done: done,
value: value
};
}
};
}
var iterator = createIterator([1, 2, 3]);
console.log(iterator.next()); // "{ value: 1, done: false }"
console.log(iterator.next()); // "{ value: 2, done: false }"
console.log(iterator.next()); // "{ value: 3, done: false }"
console.log(iterator.next()); // "{ value: undefined, done: true }"复制代码
我们的字符串,数组、类数组的对象、Set和Map,都具备Iterator接口。所以他们都是可迭代对象。
可迭代的作用有三个:
- 1.为各种数据结构,提供一个统一的、简便的访问接口;
- 2.是使得数据结构的成员能够按某种次序排列;
- 3.是ES6创造了一种新的遍历命令for...of循环,Iterator接口主要供for...of消费。
常用到iterator的场景包括:
- 1.for...of循环
- 2.扩展运算符
- 3.解构赋值
- 4.yield_yield后面跟的是一个可遍历的结构
- 5.数组的遍历会调用遍历器接口
8.Generator
严格来说generator(生成器)属于ES5,并不是ES6。但由于涉及迭代器等,所以并入es6模块。
生成器( generator )是能返回一个迭代器的函数。生成器函数由放在 function 关键字之 后的一个星号( * )来表示,并能使用新的 yield 关键字。将星号紧跟在 function 关键 字之后,或是在中间留出空格,都是没问题的.
形式上,Generator 函数是一个普通函数,但是有两个特征。一是,function关键字与函数名之间有一个星号;二是,函数体内部使用yield表达式,定义不同的内部状态(yield在英语里的意思就是“产出”)。
Generator有着"停止","开始"的状态,那我们可以用他来控制异步编程,所以,他也是异步的解决方案之一。
Generator要next一步一步往下执行。如果想一步执行,可以借助Thunk 函数(当然他的原理也是遍历帮我们执行了next。)
9.Promise
Promise 被设计用于改善 JS 中的异步编程,与事件及回调函数对比,在异步操作方面为你提供了更多的控制权与组合性。 Promise 调度被添加到 JS 引擎作业队列,以便稍后执行。不过此处有另一个作业队列追踪着 Promise 的完成与拒绝处理函数,以确保适当的执行。
Promise 具有三种状态:挂起、已完成、已拒绝。一个 Promise 起始于挂起态,并在成功时转为完成态,或在失败时转为拒绝态。在这两种情况下,处理函数都能被添加以表明Promise 何时被解决。
Promise的缺陷:
- 1)无法取消Promise,一旦新建它就会立即执行,无法中途取消。
- 2)如果不设置回调函数,Promise内部抛出的错误,不会反应到外部。
- 3)当处于pending状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)。
- 4)then的写法相比await,明显在程序代码抒写上,更加繁琐。
10.proxy 跟 Reflect
proxy: 代理对目标对象进行了虚拟,因此该代理与该目标对象表面上可以被当作同一个对象来对待。代理允许你拦截在目标对象上的底层操作,而这原本是 JS 引擎的内部能力。拦截行为使用了一个能够响应特定操作的函数(被称为陷阱)。
Reflect: 是给底层操作提供默认行为的方法的集合,这些操作是能够被代理重写的。每个代理陷阱都有一个对应的反射方法,每个方法都与对应的陷阱函数同名,并且接收的参数也与之一致。
JS 运行环境包含一些不可枚举、不可写入的 对象属性,然而在 ES5 之前开发者无法定义他们自己的不可枚举属性或不可写入属性。 ES5引入了 Object.defineProperty() 方法以便开发者在这方面能够像 JS 引擎那样做。
ES6 让开发者能进一步接近 JS 引擎的能力,这些能力原先只存在于内置对象上。语言通过代理( proxy )暴露了在对象上的内部工作,代理是一种封装,能够拦截并改变 JS 引擎的底层操作。
11.Class写法
Class写法,可以简单理解成ES6的一个语法糖。我们日常用他所实现的功能,其实用ES5都可以做到,但是class的写法,让对象原型的写法更加清晰。 但不仅仅是糖语法。
- 1.首先Class的写法会有特殊内部属性标记[[FunctionKind]]:"classConstructor",这个标记了,如果没有new,则无法调用类构造函数
- 2.类方法是不可枚举的
- 3.Class是使用严格模式的。
此外,我们需要了解一下Class写法中关键super、static、constructor、new.target。本文不做详细介绍。
2)浏览器篇
1.浏览器的储存
cookie,localStorage,sessionStorage.IndexedDB
比较一下差异:
- 1)传递方式: cookie在浏览器和服务器间来回传递; sessionStorage和localStorage不会自动把数据发给服务器,仅在本地保存;
- 2)存储大小: localStorage<=5M; sessionStorage<=5M; cookie<4K;(ie内核浏览器占主流地位,且ie6仍占有相当大的市场份额,所以在程序中应当使用少于20个cookie,且不大于4k)
- 3)有效性: localStorage:始终有效,窗口或浏览器关闭也一直保存,因此用作持久数据; sessionStorage:仅在当前浏览器窗口关闭前有效,不能持久保持; cookie:只在设置的cookie过期时间之前一直有效,即使窗口或浏览器关闭也不会消失;
- 4)共享机制: localStorage :在所有同源窗口中都是共享的; sessionStorage:同时“独立”打开的不同窗口,即使是同一页面,sessionStorage对象也是不同的; cookie:在所有同源窗口中都是共享的
- 5)浏览器支持: sessionStorage的浏览器最小版本:IE8、Chrome 5。
- 6)使用场景 cookie:保存回话信息 localStorage:持久保存的数据 sessionStorage:拥有独立特性的数据
2.浏览器的缓存
1.Service Worker 是运行在浏览器背后的独立线程。 必须HTTPS。
三个步奏:注册(下载:sw.js),监听(等其他worker失效),查看缓存 1)sw线程能够用来和服务器沟通数据(service worker的上下文内置了fetch和Push API) 2)能够用来进行大量复杂的运算而不影响UI响应。 3)它能拦截所有的请求
2.Memory Cache 将资源缓存在了内存中。事实上,所有的网络请求都会被浏览器缓存到内存中,当然,内存容量有限,缓存不能无限存放在内存中,因此,注定是个短期缓存。 内存缓存的控制权在浏览器,前后端都不能干涉。
3.Disk Cache 存储在硬盘中的缓存 强缓存和协商缓存, HTTP Header 来实现的。 Cache-Control > Expires(http1.0产物, 受本地时间影响) > ETag(http1.1出现) > Last-Modified(Last-Modified 打开文件的时候会变,以秒计算的)
4.Push Cache
服务器推送,http2
3.浏览器的渲染
生成dom树: 字节数据-->字符串-->标记(token)-->node-->dom
生成css树: 字节数据-->字符串-->标记(token)-->node-->cssdom
整体的渲染过程:
- 1)处理 HTML 并构建 DOM 树。
- 2)处理 CSS 构建 CSSOM 树。
- 3)将 DOM 与 CSSOM 合并成一个渲染树。
- 4)根据渲染树来布局,计算每个节点的位置。
- 5)调用 GPU 绘制,合成图层,显示在屏幕上。
两个重要的概念,重绘与回流:
- 重绘:当节点需要更改外观而不会影响布局的,比如改变 color 就叫称为重绘
- 回流:布局或者几何属性需要改变就称为回流。 回流必定会发生重绘,重绘不一定会引发回流。 回流所需的成本比重绘高的多,改变深层次的节点很可能导致父节点的一系列回流。 当 Event loop 执行完 Microtasks 后,会判断 document 是否需要更新。因为浏览器是 60Hz 的刷新率,每 16ms 才会更新一次。
导致性能问题:
- 1)改变 window 大小
- 2)改变字体
- 3)添加或删除样式
- 4)文字改变
- 5)定位或者浮动
- 6)盒模型
减少重绘和回流的细节:
- 1)使用 translate 替代 top
- 2)使用 visibility 替换 display: none ,因为前者只会引起重绘,后者会引发回流(改变了布局)
- 3)尽量算出结果再去重绘把 DOM 离线后修改,比如:先把 DOM 给 display:none (有一次 Reflow),然后你修改 100 次,然后再把它显示出来
- 4)动画实现的速度的选择,动画速度越快,回流次数越多,也可以选择使用 requestAnimationFrame Load 和 DOMContentLoaded 区别。Load 事件触发代表页面中的 DOM,CSS,JS,图片已经全部加载完毕。DOMContentLoaded 事件触发代表初始的 HTML 被完全加载和解析,不需要等待 CSS,JS,图片加载。
4.浏览器的安全
1.xss跨站脚本攻击 原理:
(1)构造URL
(2)发布内容式
(3)蠕虫式
2.CSRF跨站请求伪造
1)验证码。
2)HTTP Referer是header的一部分
3)token
3.sql脚本注入 拼接脚本
4.上传漏洞
(1)检查服务器是否判断了上传文件类型及后缀。 (2) 定义上传文件类型白名单,即只允许白名单里面类型的文件上传。 (3) 文件上传目录禁止执行脚本解析,避免攻击者进行二次攻击。
5.浏览器的跨域
首先什么是跨域,违反浏览器同源策略的就是跨域。跨域本身就是就是为了保护浏览器的安全, 主要是用来防止 CSRF 攻击的
那什么是同源策略?所谓的同源,指的是协议,域名,端口相同。浏览器处于安全方面的考虑,只允许本域名下的接口交互,不同源的客户端脚本,在没有明确授权的情况下,不能读写对方的资源。
解决同源策略的方案:
- 1)sonp
- 2)iframe
- 3)postMessage
- 4)CORS
- 5)webscoket
- 6)反向代理服务器
6.浏览器的内存
浏览器(通常指)的内存分配,64位系统下大约为1.4GB,在32位系统下大约为0.7G。
我们通常定义变量时候就完成了分配内存,使用时候是对内存的读写操作,内存的释放依赖于浏览器的垃圾回收机制。
造成内存泄露
- 1.意外的全局变量引起的内存泄漏。
- 2.闭包引起的内存泄漏
- 3.没有清理的DOM元素引用
- 4.被遗忘的定时器或者回调
- 5.监听事件
7.浏览器的垃圾回收
64位下新生代的空间为64M,老生代为1400M 32位下新生代的空间为16M,老生代为700M.
javaScript使用垃圾回收机制来自动管理内存,垃圾回收是一把双刃剑
- 优势:可以大幅度简化程序的内存管理代码,降低程序的负担,减少因时常运转而带来的内存泄露问题。
- 劣势:意味着程序员将无法掌控内存。js没有暴露任何关于内存的API。我们无法强迫其进行垃圾回收,也无法干预内存管理。
1、V8最初是为了浏览器设计的,不太可能遇到大内存的场景 2、js垃圾回收的时候程序会暂停线程执行,会占用一定时间。
它有两种情况会回收,一种是定时回收,一种内存不够了回收。
1.新生代算法 Scavenge GC(GC 复制算法) 分为两个空间:form 跟 to。
2.老生代算法 标记清除算法 标记压缩算法
8.浏览器的执行机制
javascript是一门单线程语言, Event Loop是javascript的执行机制libuv
需明白什么叫事件循环事件,微任务,宏任务。以及如何运行。