【ES6-阮一峰博客阅读笔记】

零、辅助学习的前置知识
1、由作用域,作用域链,变量对象,执行环境来讲闭包
  1. 闭包的概念:
    其实这里用到了一个闭包的概念,闭包就是能访问自由变量的函数,自由变量的定义为:在A作用域中使用的变量x,却没有在A作用域中声明(即在其他作用域中声明的),对于A作用域来说,x就是一个自由变量。也就是如果一个函数内部使用了非该函数作用域内的变量,那么这个变量对该函数来说就是自由变量。
    其实据我自己的查阅,这里对闭包的概念定义是有很多种的,例如,即使函数是在当前词法作用域之外执行,也能访问它所在的词法作用域内的变量,那么这个函数是一个闭包,又或者,闭包是指有权访问另一个函数作用域中变量的函数,再或者,能够访问自由变量的函数,就是一个闭包。
  2. 作用域,作用域链,变量对象,执行环境:
    贴一个讲得很好的博客博客地址,这里贴博客里面的两个图,简单来说就是,首先有一个执行环境栈,每执行一个函数,都会有一个“执行环境”被入栈,每个函数的执行环境都储存着该环境的作用域链,作用域链是一个栈,最近的作用域在栈顶,“作用域”其实是变量对象这个实体的指针,所以作用域链里其实是存了一大堆变量对象的指针,注意,作用域链是逐渐叠加的,也就是,在执行环境栈里,从底到顶如果有ABC三个函数的执行环境,那么他们的作用域链是,B的作用域链=A的作用域链+B内部的变量的对象的指针,C的作用域链=B的作用域链+C内部的变量的对象的指针。
    在这里插入图片描述
    在这里插入图片描述
  3. 作用域链是在函数被初始化时就创建了,但这时的作用域链是不完全的!比如还是从底到顶ABC,没有执行到C时,C的作用域链内只有B的作用域链,还没有创建C内部的变量对象(万一这个函数根本不执行呢,每个函数在执行之前都把变量对象全创建一遍多占内存呀)。
    还有,从底到顶ABC,是在函数B内执行的时候又发现了C,所以把C的执行环境入栈了,所以C的作用域链里,初始是它当前的外部环境,而它当前的外部环境是B的作用域链,所以初始是B的作用域链。
    如果仨函数是在全局里并排的,那么他们仨的外部环境都是全局作用域,是不会连在一块的。
    还有!如果一个函数A内有一个函数B,那么由于函数作用域的存在,B不在全局执行环境里,因此函数B目前没有被声明,也就没有初始的作用域链的!只有执行A时,B才被声明,这时候B的作用域链就可以初始化为A的作用域链(也就是执行栈,在B入栈前,栈顶A的作用域链)。
    因此,所有被声明但没有执行的函数,都有作用域链,但只有执行到它,才会创建它自己的内部变量对象,然后加在自己的作用域链栈顶,这时候作用域链才圆满了,才创建该函数的执行环境,指向这条作用域链,然后推入执行环境栈。
  4. 变量对象:
    执行环境最耗内存的就是变量对象了,理论上每一条执行环境,都是一个函数的执行触发的(最外层的script标签可以理解为一个广义的main函数嘛),而这个函数的执行会导致一个该函数内部的变量对象的产生,这个变量对象就占内存了,而作用域链上如果有其他的变量对象,也只是储存变量对象的地址,不会重复创建。
  5. 执行环境的销毁:
    理论上讲,在一个函数执行完后,它的执行环境就应该销毁了,执行它时创建的变量对象也应该释放了,但是呢,如果一个函数的作用域链里指向了其他函数的变量对象,那么,这个变量对象不会随着另一个函数的执行结束而销毁,会一直存着,这样的作用域链上有其他函数的变量对象的函数,就叫闭包,只有闭包自己执行完了,闭包自己的执行环境销毁时,作用域链上可以释放的变量对象才会被释放(如果又被其他闭包捕获了,那么还是不能释放)。
  6. 综上,闭包的三个定义也就很好理解了,所谓的可以访问自由变量,访问其他函数内的变量,可以记录执行环境等等,其实核心就是捕获,或者说‘封闭’了其他函数的变量对象,触发闭包显然会导致一部分内存无法释放,是挺占空间的,但它也有优点,例如,一个函数每次执行结束,它创建的内部变量对象就没了,但它的声明还在,作用域链就还在,上面的其他变量对象没有被清空,因此,可以把一部分想要长期保存用于该函数的对象,存在某个被封闭的变量对象内,而不用存在全局变量对象内(全局变量对象一定在所有作用域链上),这样就可以避免全局变量被污染,可以存一小部分专用于该函数的“全局”变量。
2、构造器和原型,原型链

老生常谈的知识,这里只强调一个点,构造器内this.的那些东西,是该构造器new的每个对象都单独拥有一份的,而prototype这个对象默认每个构造器都有,构造器new出来的每个对象也都有一个proto指针指向构造器的这个prototype对象,因此,为了减少new多个对象大量占用内存,可以把一些方法写在构造器的prototype里。

3、构造器函数对象的属性

如果仔细观察会发现,虽然每个对象的原型链尽头都是obj对象,但是!有一些Object自己的方法,只能通过Object.这样的方式访问,而Object是个函数呀?这就是js的一个,函数也是对象的概念!那么如何给函数对象添加属性呢?并不是在函数内通过this.,或者直接function,var之类的声明内部私有变量,其实,函数内的代码体,只是这个函数对象的属性之一,这个函数对象还可以拥有其他属性,而其他属性跟这段代码是没有关系的,因此,new出来的对象,也肯定是没法访问到这些“其他属性”了,而只有唯一的Object这个函数对象,可以访问到这些属性。
如何给一个函数对象添加属性也很简单,在声明完一个函数后,如函数名为f,只要通过f[],或者f.的方法,就把f当成一个普通对象来注册属性就可以了。但是,console.log打印这个函数名的话,会发现只会打印代码体,而不像打印普通对象一样可以看到对象内的属性,这还是有点尴尬的,所以如果要给一个函数对象注册属性,最好还是有个文档记录一下吧。
简单来说就是,this的属性在每个实例里都有,prototype的属性是每个实例里指向的都是同一个地方,而构造器对象的属性,是只属于构造器对象的,哪个实例都访问不到。

4、a instanceof B

这玩意用于判断对象B的prototype在不在a的原型链上,如果在的话,有三种可能,第一,B是构造器对象,a是B new出来的对象,那a的原型链里肯定有B的原型,第二,a不是B new出来的,但B new出来的对象是a的某个祖先或者父亲,第三,B是个普通对象,不是构造器对象,但是它在原型链上,也可以用来判断类型,比如,obj instanceof String,返回false,说明不是字符串对象…类似如此,但是由于左右两边都得是对象,所以这个判断起来还是有点局限性。

5、js垃圾回收机制

是引用计数的方式,例如一个值是个数组[1,2,3],然后有一个变量引用这个数组如arr=[1,2,3],那么这块内存的引用计数就是1,当是0时,这块内存会被垃圾回收机制定期清空,但这也导致了不好的情况,比如闭包导致某些变量对象被封闭,进而这些变量引用的内存就无法释放,进而导致内存泄漏,大量占用内存。
es6提出了一种不会影响垃圾回收的方法,即弱引用的set和map,下面正文再写。

一、let,const,和各种作用域,变量提升的相关笔记
  1. let和const:
    let和const不会变量提升,因此如果在声明前使用,会报错阻止代码运行,此外var和function会变量提升,function在前,var在后,所以函数名和变量名重复的话,无论是以怎样的顺序定义的,变量都会覆盖函数。
    还有,这个使用会报错的使用,包括任何形式的使用,比如typeof 一个let声明的变量(在声明的那行代码前),那也会报错!但如果typeof一个压根不存在的变量,反而只会打印undefined,而不是报错。
    以及不能重复声明,let,const,var,函数参数名,函数名,只要有let或者const,同层作用域就不能再声明重名变量了。
  2. 块级作用域,全局作用域,函数作用域,:
    在es5中变量提升会到函数作用域头顶或全局作用域头顶,es6中,var还是升到函数作用域或全局作用域头顶,但let和const是不会提升的,且能够识别花括号,因此有了块级作用域。
  3. 函数提升:
    上面分析了变量,这里讲下函数提升,在es5中函数的整个声明会提升到函数作用域或全局作用域头部(变量提升是只提升了变量,但值是undefined,函数是整个声明提上去),但在es6中,由于有了块级作用域,理论上函数不会穿越块级作用域提升到外面,但实际上,为了兼容老代码,这里一般是这样处理的,函数整个声明是提升到块级作用域头部,但函数名会作为一个var变量穿越块级提升到函数或全局作用域头部,如下代码,具体执行时,f是undefined,会报错,因为f作为一个函数声明提升到块级作用域头部了,但又作为一个undefined变量提升到函数作用域头部了…
function f() { console.log('I am outside!'); }
(function () {
  if (false) {
    // 重复声明一次函数f
    function f() { console.log('I am inside!'); }
  }
  f();
}());
  1. 数据类型与声明方式:
    es5只有两种声明变量的方式,var和function,es6多了let,const,import和class,现在一共是6种声明方式。数据类型分基本类型和引用类型,基本类型是number,Boolean,symbol,string,null,undefined,引用是array,function,obj。
  2. 顶层对象的属性赋值与全局变量的赋值:
    下面是阮一峰博客里的一段原文,正常来讲,给对象添加属性都是对象.或者对象[],但是全局环境下var和function声明的变量会被加进window对象,这种挂钩是不利于模块化编程的,且并不合理,因此在es6中进行了改进,首先为了兼容旧代码,var和function声明的变量还是window对象,但let,const,class声明的变量不再属于window对象。

ES6 为了改变这一点,一方面规定,为了保持兼容性,var命令和function命令声明的全局变量,依旧是顶层对象的属性;另一方面规定,let命令、const命令、class命令声明的全局变量,不属于顶层对象的属性。也就是说,从 ES6 开始,全局变量将逐步与顶层对象的属性脱钩。

  1. 未捕获的SyntaxError:词法声明不能出现在单个语句上下文中:
    就是下面这样的代码会报错,倒不是说let只能在花括号里定义,而是不能出现在这样的“单个语句上下文”,要么在花括号里,要么在全局。
if (true) let x = 1;

但下面这个在圆括号内的是可以的

for(let i=0;i<10;i++)var y=2;
二、解构赋值
十二、Symbol
  1. 它是一个永远无法重复的值,通过Symbol函数来创建,可以传入一个描述如Symbol(‘abc’),也可以不传入描述,也可以传入相同描述,无论咋样,每个Symbol()的返回值全部都是不一样的,不管传入的字符串是啥,传与不传。
  2. Symbol值不可隐式转换为字符串,但可以显式转换为字符串,如下代码,转换的字符串是带着Symbol和括号的,所有的一块全部转换成字符串,但是隐式转换(如Symbol(‘My symbol’)+‘abc’)这样是会报错的。
let sym = Symbol('My symbol');
String(sym) // 'Symbol(My symbol)'
sym.toString() // 'Symbol(My symbol)'
  1. Symbol可以隐式或显式转换为布尔值,但不可以显式或隐式转换为数字,任何一个symbol值都是true,在if里判断的话会隐式转换为true,但是不能跟数字相加,会报类型错误,它不管显式隐式都无法转换成数字。
  2. 提一嘴显式转换,就是诸如Number(),Boolean(),string()把这些构造函数直接当成函数调用,传入想要转换的变量,返回值就是转换后的,注意是返回值,不是直接把变量的值修改了,得给变量赋值才能改啊。
  3. 将symbol作为对象的属性名的几种方法,注意!不能通过obj.的方式赋值,因为.后面是跟的字符串,也就是键名,而不会解析出变量内的内容,比如obj.x,会给obj添加一个key为x的属性,而不是解析到x变量内存的值,再把这个值作为建名,虽然说起来简单,不强调一下的话有可能会注意不到的。
    因此,只能通过方括号,或obj的定义属性的方法来把一个symbol值设置为属性名。特别注意!obj[“Symbol()”]这样是不可以定义一个symbol属性的,这样就是简单的把它当作一个字符串,添加了一个属性名是这个字符串的属性,它打印后看起来仿佛一个symbol属性,但它不是!!通过Obj的getOwnPropertySymbols()的方法,会发现确实它不是一个symbol类型的属性名。
let mySymbol = Symbol();
// 第一种写法
let a = {};
a[mySymbol] = 'Hello!';
// 第二种写法
let a = {
  [mySymbol]: 'Hello!'
};
// 第三种写法
let a = {};
Object.defineProperty(a, mySymbol, { value: 'Hello!' });
  1. 用于消除魔术字符串:
    魔术字符串是一种与代码强藕合的字符串,例如判断传入值是一个具体的字符串,然后执行相应的代码,这样会导致想要通过调用这个函数来实现某些功能时,可能被迫必须传入一个固定的字符串,这就是魔术字符串。
    魔术字符串会导致一个固定的字符串与代码耦合度太高,一个较好的修改方法是改判断传入值是否与一个变量储存的值相等,如果相等,则执行对应的代码,这样与变量绑定,实际判断的字符串是可以随意修改的,代码风格就会更好。
    但是呢!如果这里只是把固定的字符串赋值给了一个变量,缺点是多个变量间的值可能会重复(在需求多个变量储存多个不同状态时,重复是会出问题的),因此,改为赋值symbol,就可以避免这种问题。
  2. 关于对象内键名为symbol类型的属性的访问方法:
    如果有一个变量储存着键名,那么用obj[]的方法就可以访问,但显然不能只依赖变量,然而它又无法通过for in或for of,或者对象+点,或者obj[‘symbol()’]之类的方式访问到,有一个简单的方法是Obj对象里的getOwnPropertySymbols(),这个方法会返回一个数组,数组里储存着所有symbol类型的键名,注意是键名不是键值。注意Object.getOwnPropertyNames()是不能获取到symbol的,只能用symbol单独的方法来获取。
  3. 实现“私有”属性:
    结合第7条可知,symbol作为键名时,常规的遍历和常规的访问方法都无法得到这个值,因此它就像一个私有属性一样,可以完成一些特殊需求。
  4. 对象的内部方法:
    如果希望一个对象内的一个方法是内部的,那么就可以用一个symbol 值作为该方法的属性名,然后,把这个属性名存在Symbol这个构造器对象内的某个变量里就可以了,这样就可以通过对象symbol.key来调用这个方法,相对来说是比较内部的,一般的简单方法很难访问到它。
  5. Symbol构造器对象内已有的一些symbol值:
    可以这样理解,我们利用symbol不好访问的特点,可以给对象注册“内部”属性,但是又得用变量存着这些symbol值,虽然get…symbol也能得到键名,但symbol键名看起来不知道是啥意思,哪怕有“描述”,描述可以重复的呀,所以还是要一个有含义的变量名存着symbol值。symbol构造器对象内就分了十几个变量出来存了十几个symbol值,这些symbol值就是对象内可能存在的内部属性,一个对象想调用的话,就得先获取到这些symbol值,然后看看自己有没有这个属性…
十三、set和map
  1. set:
    类似一个数组,可以传入一个具有遍历器接口的数据结构用于初始化,特点是会去除重复属性,用set可以很方便的去除数组内的重复值,只要再用展开符…把set展开成数组就完事了。多提一嘴重复的判断,这个判断是类似于精确相等运算符===的,也就是类型和数值都相等,区别在于NaN不精确等于NaN,但是在set里,重复的NaN会被认为重复。

Set函数可以接受一个数组(或者具有 iterable 接口的其他数据结构)作为参数,用来初始化。

  1. set实例的方法大体可以分为两类,都写在构造器原型里,一类是操作数据,包括add增,delete删,has查,clear清除。以及四个遍历方法,四个方法里三个是遍历器生成函数,也就是set部署了三个遍历器接口,keys遍历键名,values遍历键值,entries遍历键值对,通过for(let item of set.keys())这样的方式来指明通过哪个接口遍历。如果不指名的话,默认for(let item of set)的遍历器接口是values,也就是Set.prototype[Symbol.iterator]指向的是values这个遍历器生成函数。数组有forEach(),set这个进阶版无重复数组也有forEach,传入一个回调函数用于处理每个成员。

  2. 补充一句,set里面如果想加进去一个symbol值,只会让set的大小加一,里面并不能存进去这个symbol值,用下标访问打印出来是undefined。

  3. weakset是一种弱引用类型的set,里面不能存放具体值,只能存放指针,也就是广义的对象,例如数组对象,函数对象,普通的对象,它的特点在于不影响垃圾回收机制,如果没有其他引用了,那么weakset里面的成员会消失,所以可以用来临时存放成员,也可以避免内存泄漏。

  4. weakset也有set的增删查方法,但是没有清除,因为弱引用也不需要。此外也没有size和遍历,因为成员随时消失,它并不是一个可遍历对象,没有遍历器接口。

  5. map是升级版的对象,普通的obj限定键名必须是字符串,但是map的键名无所谓啥都行。它有set和get方法,set是通过传入键和值来加入一个成员,get是通过键来获取这个成员。此外也一样可以用一个数组来初始化map,数组内需要是一个个键值对子数组。

  6. 要特别注意的是,如果键名是个引用类型,必须通过同一个引用才能获取到对应的值,如map.set([‘a’], 555);
    map.get([‘a’]) // undefined
    前后两次不同的[‘a’]其实是俩引用,因此后面这个是未定义的。

  7. 简单来说,如果键是一个基本类型(简单类型),那么只要严格相等,就是同一个键,但如果是一个引用类型,则必须地址相等。此外,跟set一样,NaN和NaN会被认为相等,即是同一个键。

  8. map的方法跟set差不多,都是增删查清,还有get和set,遍历也是一样的4种,3个构造器生成函数+一个forEach。这里可以发现,map这个升级版obj是可以遍历的,它不光有遍历器接口,还一下有了仨…且遍历的线性顺序就是按照插入顺序来的。但是跟set不一样的是,map的默认for of不是值遍历,是键值对遍历。

  9. map也有一个弱引用版本weakmap,它的键名必须是对象,值无所谓,键名必须是对象,如果想要给一个对象添加一些数值,而又不是给对象添加成员,例如添加对这个对象的描述性字符串或者其他什么,就可以用weakmap来保存,weakmap既可以给对象记录一些值,又不会产生引用而影响内存回收,当这个对象不再被其他引用时,对应的weakmap成员也就消失了,很合理。

  10. weakmap的方法,跟weakset差不多,首先没有遍历,也没有size,其次也没有clear,get,set,has和delete。

十四、Proxy
十七、遍历器

遍历器实际上就是一个指针对象,该对象会包含一个next函数,每次调用next,都会返回一个对象,这个对象内储存着被遍历对象的一个成员,每次调用next都会返回下一个成员。下面是一个数组的遍历器生成函数的实现方法:

function makeIterator(array) {
  var nextIndex = 0;
  return {
    next: function() {
      return nextIndex < array.length ?
        {value: array[nextIndex++], done: false} :
        {value: undefined, done: true};
    }
  };
}

需求为:给生成函数传入一个待遍历数组,返回该数组的遍历器对象,内包含一个next函数,该函数的返回值是一个包含数组元素的对象,value储存元素的值,done储存遍历的状态(当前是否遍历到末尾)。
其实这里就用到了闭包的概念,一个专属于该构造器的,存在于被封闭的变量对象内的变量用于记录下一个需要访问的下标,每次调用next,都判断一下当前待访问下标是否超出可访问范围,如果超出,代表遍历结束。

Iterator 接口的目的,就是为所有数据结构,提供了一种统一的访问机制

如上,引用自阮一峰大佬的博客,遍历器对象其实就是一种统一的访问机制,给每个可遍历的数据结构,无论是数组,还是对象,都设置一个同名属性,且使用同名方法(如next)来访问每个元素,这种统一的访问机制,使得for…of这种访问方式得以成立

要注意的是!es6规定,可遍历对象,遍历器的属性名是一个symbol值,由上面分析可得知symbol值由于无法通过常规方式访问,是一种比较内部的写法,可以有效避免误操作把遍历器接口给覆盖了,因此,虽然强行规定遍历器接口是个字符串也行,但为了提高健壮性,还是用了symbol值,这个值被存在Symbol构造器对象的Symbol.iterator属性里了,要访问一个对象的构造器的话,或者给一个对象设置构造器,需要obj[Symbol.iterator]这样才行。

也可以说,只要一个对象/数据类型设置了这个属性,那就能通过for…of来遍历了,es6原生给一部分数据类型设置了遍历器接口,如下:
Array
Map
Set
String
TypedArray
函数的 arguments 对象
NodeList 对象
(话说,我猜啊,每个类型的遍历器接口应该是在这个类型的构造器对象里边了,然后只要把这个类型的实例,传给这个类型的构造器的遍历器生成函数就行了。)
可见普通的自定义构造器生成的对象不能遍历,还有Object这个构造器也没有遍历接口。

对象(Object)之所以没有默认部署 Iterator 接口,是因为对象的哪个属性先遍历,哪个属性后遍历是不确定的,需要开发者手动指定。本质上,遍历器是一种线性处理,对于任何非线性的数据结构,部署遍历器接口,就等于部署一种线性转换。不过,严格地说,对象部署遍历器接口并不是很必要,因为这时对象实际上被当作 Map 结构使用,ES5 没有 Map 结构,而 ES6 原生提供了。一个对象如果要具备可被for…of循环调用的 Iterator 接口,就必须在Symbol.iterator的属性上部署遍历器生成方法(原型链上的对象具有该方法也可)。

简单来说,普通的对象并不是一个线性结构,因此不具备默认的线性遍历方式,但如果有相关需求,也可以给一个对象添加遍历。
如下是给链表的节点对象部署遍历接口,因为从任何一个节点开始都能够遍历,所以遍历器接口可以部署在节点构造函数的原型上。
构造器接口函数的返回值是一个对象,对象里有一个next函数,next的返回是一个个元素对象,这个最外层的接口函数内的变量对象空间,可以储存一些next函数专用的变量,通常会储存一个标识当前遍历状态的变量,比如链表的遍历需要储存一个下一个待遍历节点的指针,数组的遍历需要储存下标,遍历图或树的话可以根据需求设置下一个遍历状态的寻找和储存。

function Obj(value) {
  this.value = value;
  this.next = null;
}
Obj.prototype[Symbol.iterator] = function() {
  var iterator = { next: next };
  var current = this;
  function next() {
    if (current) {
      var value = current.value;
      current = current.next;
      return { done: false, value: value };
    }
    return { done: true };
  }
  return iterator;
}
var one = new Obj(1);
var two = new Obj(2);
var three = new Obj(3);
one.next = two;
two.next = three;
for (var i of one){
  console.log(i); // 1, 2, 3
}

下面再贴个类数组对象的遍历器函数,因为是遍历这个对象内的内容,所以接口部署在对象内部就行了。

let obj = {
  data: [ 'hello', 'world' ],
  [Symbol.iterator]() {
    const self = this;
    let index = 0;
    return {
      next() {
        if (index < self.data.length) {
          return {
            value: self.data[index++],
            done: false
          };
        }
        return { value: undefined, done: true };
      }
    };
  }
};

再强调一点,给遍历器接口传入一个待遍历对象后,返回的是一个包含next函数的对象啊!这时候遍历还没开始呢。

遍历器对象除了具有next()方法,还可以具有return()方法和throw()方法。如果你自己写遍历器对象生成函数,那么next()方法是必须部署的,return()方法和throw()方法是否部署是可选的。

再引大佬博客的一段原文如上,也就是,遍历器函数返回的包含几个方法的对象,next是必须的,此外还可以部署return和throw,如下是return:

function readLinesSync(file) {
  return {
    [Symbol.iterator]() {
      return {
        next() {
          return { done: false };
        },
        return() {
          file.close();
          return { done: true };
        }
      };
    },
  };
}

先不管传入的是啥,return函数会返回遍历终止的对象,也就是提前结束遍历,在以下两个情况下,由于遍历被提前终止了,就会调用遍历器的return方法。

for (let line of readLinesSync(fileName)) {
  console.log(line);
  break;
}
// 情况二
for (let line of readLinesSync(fileName)) {
  console.log(line);
  throw new Error();
}

一个是break会中途停止循环,另一个throw抛出错误也会停止循环,这种时候就会调用return。

补充一个所有的遍历方法:

  1. 最基本的用游标的for循环,for(let i=0;i<n;i++),写起来比较麻烦
  2. forEach:array.forEach(function(currentValue, index, arr), thisValue),可以用于遍历数组,传给forEach一个函数,可以对数组内的每个元素(元素是第一个传入参数,第二个是下标,第三个是被遍历的这个数组,后面俩是可选的)作出处理,算是基础for的一种升级写法,但是缺点是没法中途终止循环。
  3. for…in:这个可以遍历键名,但是主要是遍历对象的,如果遍历数组的话,可能会出现下面的问题:
    给数组的原型上加个对象,然后for in遍历,会把加的这个也遍历到的,显然不大合理。
var c=[1,2,3]
	c.__proto__.p=2
	for(let key in c){
		console.log(key)
	}
console.log(c)

还有因为__proto__跟数组构造器的prototype指向同一个对象,所以也可以Array.prototype.p=2 这样也一样。也就是手动添加进原型链的那些对象都会被遍历到,c.__proto__={x:2,y:3} 这样直接改掉整个原型对象的话,就会把x和y遍历到。所以显然不适合遍历数组。

  1. for…of:又简洁,又只按需求遍历,原本没法遍历的结构只要部署遍历接口就也能遍历了,就很强。
十八、Generator

这玩意的出现让遍历器的实现更加丝滑了,如下:

let myIterable = {
  [Symbol.iterator]: function* () {
    yield 1;
    yield 2;
    yield 3;
  }
};

这个星号函数会返回一个包含next的对象,也就是遍历器,再next()一下就会返回一个包含值和done的对象,内部实现是这样,发现yield时,就把它后面的东西作为值,然后返回一个值和done的对象。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值