阅读前言
转眼间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,中文称搜索引擎优化,一种利用搜索引擎的搜索规则来提高目前网站在有关搜索引擎内的自然排名的方式。他的实现原来分别为,页面抓取,分析入库,检索排序。
有兴趣深入SEO优化的朋友:segmentfault.com/a/119000001…
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等。
具体元素跟写法有兴趣,可参考:blog.csdn.net/qq_27674439…
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函数:www.cnblogs.com/chaoguo1234…
执行上下文,可分为三个阶段,分别为创建,执行,销毁阶段。我们简单的分析一下,各个阶段分别处理了什么。
- 创建阶段: (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关键字
- 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 语句,会抛出错误。