Fork me on GitHub

前端知识体系建设知识提纲(汇总)

欢迎关注前端早茶,与广东靓仔携手共同进阶

前端早茶专注前端,一起结伴同行,紧跟业界发展步伐~

一.前端基础

前端基础,个人认为就是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,中文称搜索引擎优化,一种利用搜索引擎的搜索规则来提高目前网站在有关搜索引擎内的自然排名的方式。他的实现原来分别为,页面抓取,分析入库,检索排序。

如何优化SEO:

1)title、description、keywords 2)利用好html语义化 3)重要的东西放前面 4)少用iframe

 

3.doctype

前端经常在html头部看到DOCTYPE的声明,一般常位于文档的第一行。那么他的作用是什么,可能对新的浏览器或者新的网站暂无什么影响,但是相对古老的浏览器或者是网站,可能会出现不同。因为浏览器有标准模式与兼容模式,差异相对比较大。

标准模式的渲染方式和 JS 引擎的解析方式都是以该浏览器支持的最高标准运行。 兼容模式中,页面以宽松的向后兼容的方式显示 ,模拟老式浏览器的行为以防止站点无法工作。

而DOCTYPE的存在,就是为了声明,该页面使用标准模式。不声明,可能一些旧的网站会出现兼容模式。

4.link与@import

link与import , 本质使用上,我们都是用他来引入css,但是他们有一定的区别。

  1. link是一种引入资源的标签,import是引入css的方式。所以,import引入的只能是css,而link可以引入所有的资源,包括图片,RSS等。

  2. 加载顺序上也有一些差异。 link引用的CSS会同时被加载。 import引用的CSS会等到页面全部被下载完再加载。

  3. 兼容性的差别。 link无任何兼容问题,import兼容IE5以上。(当然,IE5估计也找不到了)

  4. 动态引入样式 link可以后期引入样式,而import是不可以后期引入的,只能初始化页面之前引入。

  5. 复用率的问题 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。

执行顺序:捕捉--》目标--》冒泡

event.stopPropagation()阻止事件的传递行为. event.preventDefault();阻止默认行为,比如阻止a的href

  • 优点: 1.减少事件注册,节省内存。例如上面代码,只指定 父元素的处理程序,即可管理所有所有子元素的“click”事件; 2.简化了dom节点更新时,相应事件的更新

  • 缺点: 1.利用事件冒泡的原理,不支持不冒泡的事件; 2.层级过多,冒泡过程中,可能会被某层阻止掉; 3. 理论上委托会导致浏览器频繁调用处理函数,虽然很可能不需要处理。所以建议就近委托,比如在ol上代理li,而不是在document上代理li。 4. 把所有事件都用代理就可能会出现事件误判。比如,在document中代理了所有button的click事件,另外的人在引用改js时,可能不知道,造成单击button触发了两个click事件。

7.渐进增强与优雅降级

渐进增强:针对低版本浏览器进行构建页面,保证最基本的功能,然后再针对高级浏览器进行效果、交互等改进,达到更好的用户体验。 优雅降级:一开始就构建完整的功能,然后再针对低版本浏览器进行兼容。

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包含width , 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)也会失效。

 

特别注意: 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 的 linear-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.构造函数继承 在子类构造函数的内部调用超类型构造函数。使用apply()和call() 方法 缺点: 1)函数复用性不高 ,每个实例都是重新实例化构造函数,不存在共享属性 2)只能继承实例上的属性,原型上的方法不可见

  • 3.组合继承 本质:原型链 + 构造函数 Parent.call(this) new Parent()避免了上述的缺点,常用。 优点:可传参,不会与父类引用属性共享 缺点:继承父类函数的时候调用了父类构造函数,导致子类的原型上多了不需要的父类属性,存在内存上的浪费。

  • 4.原型式继承 实现本质:object()函数对传入其中的对象执行了一次浅复制

  • 5.寄生式继承 借用构造函数来继承属性,通过原型链的混成形式来继承方法

  • 6.寄生组合 高效率只调用了一次构造函数,集寄生式继承和组合继承的优点于一身,是实现基于类型继承的最有效方式。 就是将父类的原型赋值给了子类,并且将构造函数设置为子类,这样既解决了无用的父类属性问题 Parent.call + Object.create()

  • 7.class继承

7.this关键字

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方法)

补充: 有人提到ToPrimitive的过程,ToPrimitive有两种情况,一个是Number,一个是String。

如果是Number,执行如下:

  1. 如果input是原始值,直接返回这个值;
  2. 否则,如果input是对象,调用input.valueOf(),如果结果是原始值,返回结果;
  3. 否则,调用input.toString()。如果结果是原始值,返回结果;
  4. 否则,抛出错误。

如果是String, 则步骤2跟3调换。

那么什么时候ToPrimitive的类型是String,什么时候type类型是Number。

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.数组

数组的方法可以写的实在是太多了。 

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来创建一些常量。比如订单状态等,可以也可以避免重复。 3.作为私有变量,防止暴露

4.数组的扩展

需要明白Array.of跟Array.from的意义。

首先上述提到,数组有了伪数组的概念,而转化为数组,可以通过 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)jsonp
  • 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

需明白什么叫事件循环事件,微任务,宏任务。以及如何运行。



三.巩固前端基建

本章重点,提供的知识点的归类,以及重点。具体的知识点不做多余的介绍。(如果对知识点不熟悉,建议重点突击补充一下。)

1)加深巩固篇

1.前端去重的方法有哪些?

可行的去重方法(关键字思维,代码太多了暂不提供):

  • 1.利用Set,自带数组去重功能。也是ES6最实用的方法
  • 2.利用Map,
  • 3.for for splice/del
  • 4.indexOf
  • 5.sort
  • 6.includes
  • 7.hasOwnProperty
  • 8.filter
  • 9.递归

2.前端异步的方案有哪些?

1)Promise(可看上篇的解释) 2)generator(可看上篇的解释) 3)async/await 4)事件发布/监听模式(可看后续的设计模式,属于发布订阅模式,代表作EventBus)

async 和 await 相比直接使用 Promise 来说,优势在于处理 then 的调用链,能够更清晰准确的写出代码。缺点在于滥用 await 可能会导致性能问题,因为 await 会阻塞代码,也许之后的异步代码并不依赖于前者,但仍然需要等待前者完成,导致代码失去了并发性。

3.前端网络请求有哪些?

ajax, fetch, axios。

一句简单的语句来概括的话,ajax因为不支持promise给淘汰,从而有了fetch,然后fetch缺陷多,不支持对异常捕捉,不支持监听进度条,cookies不友好等等。所以,axios当今浪潮。

 

4.前端定时器有哪些?

异步编程当然少不了定时器了,常见的定时器函数有 setTimeout、setInterval、requestAnimationFrame。我们先来讲讲最常用的setTimeout,很多人认为 setTimeout 是延时多久,那就应该是多久后执行。 其实这个观点是错误的,因为 JS 是单线程执行的,如果前面的代码影响了性能,就会导致 setTimeout 不会按期执行。当然了,我们可以通过代码去修正 setTimeout,从而使定时器相对准确

首先 requestAnimationFrame 自带函数节流功能,基本可以保证在 16.6 毫秒内只执行一次(不掉帧的情况下),并且该函数的延时效果是精确的,没有其他定时器时间不准的问题,当然你也可以通过该函数来实现 setTimeout。

5.前端创建对象有哪几种方式?

该知识点只做汇总提示,不做具体分析。请各自查询资料查漏补缺。

  • 1)var obj = new Object();

  • 2)var obj = { name: '小明' }

  • 3)工厂模式

     function createObj(name){
          var o = new Object();
          o.name = name;
          o.fun = function(){
          }
          return o;
      }

     

  • 4)构造函数

    function TestObj(name){
          this.name = name;
      }
     
  • 5)原型创建

      function TestObj(){}
              Person.prototype.name = '小明';
      };
     
  • 6)构造函数 + 原型创建

  • 7)class写法

6.前端的继承方式有哪些?

  • 1.原型链继承 本质是重写了对象。 缺点: 1)对象实例共享所有继承的属性和方法
    2)不能传递参数

  • 2.构造函数继承 在子类构造函数的内部调用超类型构造函数。使用apply()和call() 方法 缺点: 1)函数复用性不高 ,每个实例都是重新实例化构造函数,不存在共享属性 2)只能继承实例上的属性,原型上的方法不可见

  • 3.组合继承 本质:原型链 + 构造函数 Parent.call(this) new Parent()避免了上述的缺点,常用。 优点:可传参,不会与父类引用属性共享 缺点:继承父类函数的时候调用了父类构造函数,导致子类的原型上多了不需要的父类属性,存在内存上的浪费。

  • 4.原型式继承 实现本质:object()函数对传入其中的对象执行了一次浅复制

  • 5.寄生式继承 借用构造函数来继承属性,通过原型链的混成形式来继承方法

  • 6.寄生组合 高效率只调用了一次构造函数,集寄生式继承和组合继承的优点于一身,是实现基于类型继承的最有效方式。 就是将父类的原型赋值给了子类,并且将构造函数设置为子类,这样既解决了无用的父类属性问题 Parent.call + Object.create()

 

7.前端代码的复用有哪几种方式?

该知识点只做汇总提示,不做具体分析。请各自查询资料查漏补缺。

  • 1)函数封装
  • 2)继承
  • 3)复制extend
  • 4)混入mixin
  • 5)借用apply/call

2)手写api

此部分,废话不多说,直接给对应的代码!如果还有其他重要手写代码,留言笔者会考虑补充。

有源码不理解的朋友们,可自行查询资料

1.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;
}

2.apply/call/bind

Function.prototype.wzApply = function (context) {
        const thisContext = context ? context : windows;
        thisContext.fn = this;
        var result = null;
        if (arguments[1]) {
            result = thisContext.fn(...arguments[1]);
        } else {
            result = thisContext.fn();
        }
        delete thisContext.fn;
        return result;
    }

    Function.prototype.wzCall = function (context) {
        const thisContext = context ? context : windows;
        thisContext.fn = this;
        var result = null;
        var args = [...arguments].slice(1)
        if (args) {
            result = thisContext.fn(...args);
        } else {
            result = thisContext.fn();
        }
        delete thisContext.fn;
        return result;
    }

    Function.prototype.wzBind = function (context) {
        var _this = this;
        var arg = [...arguments].slice(1);
        return function F(){
            if( this instanceof F ){
                return new _this(arg.concat(...arguments));
            }
            return _this.apply( context, arg.concat(...arguments) );
        }
    }

3.instanceOf

function _instanceof(A, B) {
        var O = B.prototype;// 取B的显示原型
        A = A.__proto__;// 取A的隐式原型
        while (true) {
            //Object.prototype.__proto__ === null
            if (A === null)
                return false;
            if (O === A)
                return true;
            A = A.__proto__;
        }
    }
 

4.获取url参数

function getQuery( params ){
       var query = window.location.search.substring(1);
       var vars = query.split("&");
       for (var i=0;i<vars.length;i++) {
               var pair = vars[i].split("=");
               if(pair[0] == params  ){return pair[1];}
       }
       return(false);
}
 

5.模拟深拷贝

function deepClone(initalObj, finalObj) {    
  var obj = finalObj || {};    
  for (var i in initalObj) {        
    var prop = initalObj[i];        // 避免相互引用对象导致死循环,如initalObj.a = initalObj的情况
    if(prop === obj) {            
      continue;
    }        
    if (typeof prop === 'object') {
      obj[i] = (prop.constructor === Array) ? [] : {};            
      arguments.callee(prop, obj[i]);
    } else {
      obj[i] = prop;
    }
  }    
  return obj;
}
 

6.防抖与节流

const debounce = (cb, wait = 500, immediate = true) => {
        let timer = 0;
        let context = null;
        let param = null;
        const later = () => setTimeout(() => {
            timer = null;
            if (!immediate) {
                cb.apply(context, param);
                context = null;
                param = null;
            }
        }, wait);

        return function (...args) {
            if (!timer) {
                timer = later();
                if (immediate) {
                    cb.apply(this, args);
                } else {
                    context = this;
                    param = args;
                }
            } else {
                clearTimeout(timer);
                time = later();
        }
    }
}

const throttle= (func,  delay=500 ){
        var timer = 0;
        var startTime = Date.now();
        return function( ...args ){
                var context = this;
                var currentTime =  Date.now();//当前时间
                var diffTime = currentTime - startTime; //时间差
                clearTimeout(timer);
                if( diffTime > delay){
                    func.apply( context, args);
                    startTime =  Date.now();
                }else{
                    timer = setTimeout( function(){
                        throttle( func, delay);
                });
        }
    }
}
 

7.手写迭代器next

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]);
 

8.手写Object.freeze

function tFreeze(obj){
  if(obj instanceof Object){
    Object.seal(obj);  // 封闭对象
    for(let key in obj){
      if(obj.hasOwnProperty(key)){
        Object.defineProperty(obj,key,{
          writable:false 
        })
        myFreeze(obj[key]);  //遍历
      }
    }
  }
 

3)框架源码理解

源码是很多企业的考试重点,看不懂源码没关系,但是要明白源码大概是什么,大概的原理又是什么。此章节内容相对较多,笔者就不单独在本文解析。

笔者自身也处于探索或学习状态。如果你2到5年工作经验,想学习一下源码,可以参考一下笔者过去写的文章,那也是笔者的复习思路。

列的知识点,都是重点之重点。

如果你有一定的基础,或以及看过源码,或觉得网上还有更好的帖子,可以另从他处学习,不喜勿喷。 **(笔者也是学习中写下的源码,有不足或者理解错误的地方,多多包涵与讨论) **

1.vue源码

2.react源码

3.wepback源码

4.vuex源码

5.vue route源码

6.diff源码

7.promise源码

8.react体系

react fiber,,react redux, react Hook等

4)工具拓展篇

1.babel

Babel是一个工具链,主要用于将ECMAScript 2015+版本代码向后兼容 Javascript 语法,以便可以运行到旧版本浏览器或其他环境中。 Babel 的三个主要处理步骤分别是: 解析(parse),转换(transform),生成(generate)。

  • 解析

将代码解析成抽象语法树(AST),每个js引擎(比如Chrome浏览器中的V8引擎)都有自己的AST解析器,而Babel是通过Babylon实现的。在解析过程中有两个阶段:词法分析和语法分析,词法分析阶段把字符串形式的代码转换为令牌(tokens)流,令牌类似于AST中节点;而语法分析阶段则会把一个令牌流转换成 AST的形式,同时这个阶段会把令牌中的信息转换成AST的表述结构。

  • 转换

在这个阶段,Babel接受得到AST并通过babel-traverse对其进行深度优先遍历,在此过程中对节点进行添加、更新及移除操作。这部分也是Babel插件介入工作的部分。

  • 生成

将经过转换的AST通过babel-generator再转换成js代码,过程就是深度优先遍历整个AST,然后构建可以表示转换后代码的字符串。

  • babel编译原理 ● babylon 将 ES6/ES7 代码解析成 AST ● babel-traverse 对 AST 进行遍历转译,得到新的 AST ● 新 AST 通过 babel-generator 转换成 ES5

  • babel/babel-polyfill

  • babel-polyfill:ES6的转码。IE的兼容

2.nignx

非专业人员,不过前端人员还是必要掌握一下前端如何部署。

简单的普及一下nignx,Nginx可见简单理解成,所开发高性能的 Web和 反向代理 服务器。

明白nigix如何实现正向代理,如何实现反向代理,如何完成负载均衡。

3.csr与ssr

SSR(Server Side Rendering) :传统的渲染方式,由服务端把渲染的完整的页面吐给客户端。这样减少了一次客户端到服务端的一次http请求,加快相应速度,一般用于首屏的性能优化。

CSR(Client Side Rendering):是一种目前流行的渲染方式,它依赖的是运行在客户端的JS,用户首次发送请求只能得到小部分的指引性HTML代码。第二次请求将会请求更多包含HTML字符串的JS文件。

SSR优点: 1)有利于SEO的优化 2)首屏快

缺陷: 1)性能全都依赖于服务器 2)只能做静态,交互效果还是得用CSR,前端界面开发可操作性不高 3)开发条件受限,生命周期等。

反之,CSR 不利于SEO,首屏慢,但是交互效果好。

4.web Worker

 

四.编程相关

这里为"编程相关",而不是"前端相关",因为这是无论哪个客户端的开发人员都必须掌握的基础。废话不多说,本文列举以前端挂钩的知识点。

1)设计模式篇

设计模式更是一种思维。设计模式一共23种,分创建型,结构,行为型。本文举几个,前端用到的几个设计模式,且是简单的描述一下。

1.单例模式

一个极有可能重复出现的“实例”, 如果重复创建,是否消耗性能?如果借助第一次的实例,后续只是对该实例的重复使用,这样就达到了我们节省性能的目的。

可能服务端的朋友,知道什么是数据库链接池,这就是一个单例的经典模式,数据库连接池,与每次创建数据库连接,效率值将差异巨大。这就是单例模式的魅力。

我们前端的实践中,也经常可以借鉴这个思维。例如,登录弹出框,取消重新弹出时,再显示原来的弹出框,而不是重新创建。

var myLogin = function( fn ){
        var result;
        return function(){
            return result || ( result = '<div>我是登陆框</div>' );
    }
}

2.工厂模式

这里严格来说有两类设计模式,一个叫简单工厂,一个抽象工厂。

简单工厂模式叫做静态工厂方法模式,是由一个工厂对象决定创建出哪一种产品类的实例。例如富士康同时需要生产华为,苹果等手机,我们可用一个工厂的模型,生产人只要输入型号,就可以产出对应的手机。

抽象工厂,多一个抽象对象。抽象工厂是工厂方法模式的核心,所有创建对象的工厂类都必须实现该接口。 如上栗子,如果还需要手机颜色,内存大小,共同来确定一步手机,这就是抽象工厂。 抽象工厂,还需要使用户根据参数获得对应的类实例,避免了直接实例化类,降低了耦合性。

3.策略模式

简单点的理解,就是针对不同的状态,给出不同的算法或者结果。定义好策略组,根据不同的状态定义不同的策略。我们前端的实例,比如我们的form验证。 他的优点:

  • 1、算法可以自由切换。
  • 2、避免使用多重条件判断。
  • 3、扩展性良好。

缺点:

  • 1、策略类会增多。
  • 2、所有策略类都需要对外暴露。

4.责任链模式

用来处理相关事务责任的一条执行链。例如前端JS 中的事件冒泡,一层一层往上传递。 优点:

  • 1、降低耦合度。它将请求的发送者和接收者解耦。
  • 2、简化了对象。使得对象不需要知道链的结构。
  • 3、增强给对象指派职责的灵活性。通过改变链内的成员或者调动它们的次序,允许动态地新增或者删除责任。
  • 4、增加新的请求处理类很方便。

缺点: 不能保证请求一定被接收;代码调试时不太方便,可能会造成循环调用;

5.观察者模式(Vue必懂)

  • 观察者模式(Vue双向绑定原理了解一下): 观察者(Observer)直接订阅(Subscribe)主题(Subject),而当主题被激活的时候,会触发(Fire Event)观察者里的事件。

  • 发布订阅模式: 订阅者(Subscriber)把自己想订阅的事件注册(Subscribe)到调度中心(Topic),当发布者(Publisher)发布该事件(Publish topic)到调度中心,也就是该事件触发时,由调度中心统一调度(Fire Event)订阅者注册到调度中心的处理代码。

发布订阅模式属于广义上的观察者模式。经过时间的沉淀,慢慢独立于观察者模式,成为另外一种不同的设计模式。

在这里插入图片描述 

Vue双向绑定的思维的设计模式,Vue玩家必须深入一下。

7.装饰器模式(React必懂)

装饰器模式(Decorator Pattern)允许向一个现有的对象添加新的功能,同时又不改变其结构。 有点类似我们的相框与相片的关系。

  • 优点:装饰类和被装饰类可以独立发展,不会相互耦合。
  • 缺点: 多层装饰比较会显得复杂。

React高阶组件的思维,React玩家必须深入一下。

2)网络协议篇

1.http特点

优点

  • 1.简单快速

每个资源得到URI是固定的,想访问某个资源,只需要输入这个资源对应的URI就可以了。 (URL(Uniform Resource Location)统一资源定位符,URI(Uniform Resource Identifier)统一资源标识符。URL是URI的子集,URL就是用定位的方式实现的URI。)

  • 2.灵活

每个HTTP头部有个Content-Type,一个HTTP协议通过设置不同的Content-Type值,可以完成不同类型的资源的传输。

  • 3.无连接

限制每次连接只处理一个请求。服务器处理完客户的请求,并收到客户的应答后,即断开连接。采用这种方式可以节省传输时间。

  • 4.无状态

HTTP协议对于事务处理没有记忆能力。知乎一句简单回单很好:就是第二次来你无法识别它曾经来过。(人生若只如初见) 但是,在通过增加cookie和session机制的前提下,现在的网络请求其实是有状态的。

缺点

  • 1.无状态,有时候,需要保存信息,比如像购物系统,需要保留下顾客信息等等,另外一方面,有时候,无状态也会减少网络开销,比如类似直播行业这样子等,这个还是分场景来说。
  • 2.明文传输,即协议里的报文(主要指的是头部)不使用二进制数据,而是文本形式。这让HTTP的报文信息暴露给了外界,给攻击者带来了便利。
  • 3.队头阻塞,当http开启长连接时,共用一个TCP连接,当某个请求时间过长时,其他的请求只能处于阻塞状态,这就是队头阻塞问题。

2.https

  • HTTPS协议需要到CA申请证书,一般免费证书很少,需要交费。
  • HTTP协议运行在TCP之上,所有传输的内容都是明文,HTTPS运行在SSL/TLS之上,SSL/TLS运行在TCP之上,所有传输的内容都经过加密的。
  • HTTP和HTTPS使用的是完全不同的连接方式,用的端口也不一样,前者是80,后者是443。
  • HTTPS可以有效的防止运营商劫持,解决了防劫持的一个大问题。

3.http1.1

  • 缓存处理
  • 带宽优化
  • 异常码的完善,新增了24个错误状态响应码,
  • Host头处理
  • 长连接

4.http2.0

  • header压缩
  • 新的二进制格式
  • 多路复用
  • 服务端推送

5.http缓存

参考上篇,浏览器缓存部分。

6.常见状态码

「1xx」: 代表请求已被接受,需要继续处理。 「2xx」: 表示成功状态。 「3xx」: 重定向状态。 「4xx」: 客户端错误。 「5xx」: 服务器端错误。

7.DNS解析

浏览器缓存 —>> 本地hosts文件 —>> 本地DNS解析器 —>>本地DNS服务器 —>> 其他域名服务器请求。

8.三次握手

  • 第一次握手:建立连接时,客户端发送syn包(seq=j)到服务器,并进入SYN_SENT状态,等待服务器确认;SYN:同步序列编号(Synchronize Sequence Numbers)。
  • 第二次握手:服务器收到syn包,必须确认客户的SYN(ack=j+1),同时自己也发送一个SYN包(seq=k),即SYN+ACK包,此时服务器进入SYN_RECV状态。
  • 第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=k+1),此包发送完毕,客户端和服务器进入ESTABLISHED(TCP连接成功)状态,完成三次握手。

9.四次挥手

  • 1、客户端发送断开TCP连接请求的报文,其中报文中包含seq序列号,是由发送端随机生成的,并且还将报文中的FIN字段置为1,表示需要断开TCP连接。(FIN=1,seq=x,x由客户端随机生成)
  • 2、服务端会回复客户端发送的TCP断开请求报文,其包含seq序列号,是由回复端随机生成的,而且会产生ACK字段,ACK字段数值是在客户端发过来的seq序列号基础上加1进行回复,以便客户端收到信息时,知晓自己的TCP断开请求已经得到验证。(FIN=1,ACK=x+1,seq=y,y由服务端随机生成)
  • 3、服务端在回复完客户端的TCP断开请求后,不会马上进行TCP连接的断开,服务端会先确保断开前,所有传输到A的数据是否已经传输完毕,一旦确认传输数据完毕,就会将回复报文的FIN字段置1,并且产生随机seq序列号。(FIN=1,ACK=x+1,seq=z,z由服务端随机生成)
  • 4、客户端收到服务端的TCP断开请求后,会回复服务端的断开请求,包含随机生成的seq字段和ACK字段,ACK字段会在服务端的TCP断开请求的seq基础上加1,从而完成服务端请求的验证回复。(FIN=1,ACK=z+1,seq=h,h为客户端随机生成)

五.前端框架(复习指引)

该章节,本计划列出所有知识点。后续发现有很多好文,再加上笔者时间不足, 避免本文没了下文,笔者做一个简单的指引。(该章节后续有时间,会单独出文章,时间暂不确定,有兴趣就期待吧)

1)Vue

2)React

 

欢迎关注前端早茶,与广东靓仔携手共同进阶

前端早茶专注前端,一起结伴同行,紧跟业界发展步伐~

 
posted @ 2021-12-23 14:35  广东靓仔-啊锋  阅读(160)  评论(0编辑  收藏  举报