es6 笔记

一, 函数参数的默认值

1,与解构赋值默认值结合使用

  function foo ({x=5, y = 5} = { }) { console.log(x,y) } 

  function foo({x,y} = {x:5, y : 5}) { console.log(x, y)}

  上面两种写法都对函数的参数设定了默认值,不同的是,第一种写法设置的默认值为空对象,但是也设置了对象解构赋值的默认值。 第二种写法的参数默认值是一个有具体属性的对象,但没有设置对象解构赋值的默认值。

2,参数默认值的位置

  通常情况下,定义了默认值的参数应该是函数的尾参数。

3,函数的length属性

  指定了默认值后,函数的length属性将返回没有指定默认值的参数个数。

4,作用域

  设置了参数默认值后,函数进行初始化声明时,参数会形成一个单独的作用域。

5,应用

  可以利用参数默认值 指定某一个参数不得省略,如果省略就抛出错误。

   function throwIfMissing () { throw new Error (' missing parameter')}

  function fn( mustBeProvided = throwIfMissing() ) { return mustBeProvided; }

  调用fn的时候,如果没有参数,就会调用默认值函数throwIfMissing,从而抛出错误。

  也可以将参数默认值设为undefined,表明这个参数是可以省略的。

6,rest参数

  rest参数用于获取函数的多余参数,这样就不需要使用arguments对象了,rest参数搭配的变量是一个数组。

  注意: rest参数之后不能再有其他的参数(只能是最后一个参数),否则会报错。

7,严格模式

  es6中只要函数参数使用了默认值,结构赋值或者扩展运算符,那么函数内部就不能显示设定为严格模式了。

  有两种方法可以避免:

    1,设定全局的严格模式

    2,把函数包在一个无参数的立即执行函数里面。

8,name 属性

  函数的name属性返回该函数的函数名

  如果将一个有名函数赋值给一个变量,则返回该函数原来的名字

9,箭头函数

  es6允许使用   =>  定义函数

  如果箭头函数不需要参数或者需要多个参数,就要使用圆括号代表参数部分

  如果箭头函数的代码块多于一条语句,就要使用大括号括起来,并使用return 返回。

  由于大括号被解释为代码块,如果箭头函数直接返回一个对象,必须在对象外面加上括号。

  箭头函数的一个用处是用来简化回调函数。

  rest参数和箭头函数结合: const n = (...nums) => nums;

  注意:

    箭头函数中的this是定义时所在的对象,而不是使用时所在的对象

    箭头函数不能用来当做构造函数,因为没有this

    不能使用arguments对象,可以用rest参数来代替

    不能使用yield命令,因此箭头函数不能用作Generator函数。

10,尾调用优化

  尾调用就是指某个函数的最后一步是调用另一个函数。

    function f(x) { return g(x) }

    尾调用不一定出现在函数的尾部,只要是最后一步即可。

    由于尾调用是函数的最后一步操作,所以不需要保留外层函数的调用帧等信息,直接用内容函数的调用帧取代即可。

    如果所有的函数都是尾调用,那么可以做到每次执行时调用帧只有一项,这就是尾调用优化。

    (只有不再用到外层函数的内部变量,内层函数的调用帧才会取代外层函数的)

 

二,数组的扩展

1,扩展运算符

  表示为三个点(...),如同rest参数的逆运算,将一个数组转为用逗号分隔的参数序列。

  由于扩展运算符可以展开数组,所以就不再需要apply将数组转为函数的参数。

  Math.max(...[12,3,4,5])

2,扩展运算符的应用

  合并数组:    [ ...arr1, ...arr2, ...arr3]

  与解构赋值结合来生成数组。   必须将其放在参数的最后一位。

  扩展运算符还可以将字符串转为真正的数组。

  任何Iterator接口的对象都可以用扩展运算符转为真正的数组。

  如果没有部署Iterator接口的类数组对象则不行, 可以使用Array.from() 转为真正的数组。

3,Array.form()

  用于将两类对象转为真正的数组: 类数组对象和 可遍历对象。

  可以接收第二个参数,作用类似于数组的map方法。 如果map中用到了this,可以传入第三个参数 来绑定this

  另一个应用为: 将字符串转化为数组,返回字符串的长度。

4,Array.of ()

  用于将一组值转化为数组。 用来补充 Array构造函数的不足。

5,find和findIndex

  find方法用于找到第一个符合条件的数组成员。它的参数是一个回调函数。所有参数依次执行该回调函数知道找出第一个返回值为true的成员,返回该成员。如果没有符合的,则返回undefined。

  findIndex类似于find, 只不过返回的是成员的位置。 如果所有都不符合,则返回-1。

  这两个方法都可以接收第二个参数,用来绑定回调函数的this。

  这两个方法都可以发现NaN(借助Object.is()), 弥补了IndexOf的不足。

6, fill()

  fill用来使用给定值填充数组。

  fill方法用于空数组的初始化非常方便。数组中已有的元素将会被抹去。

  可以接受第二个和第三个参数。 用于指定填充的开始位置和结束位置。

7,entries()、keys()和values()

  用于遍历数组。返回一个遍历器对象。可以用for of循环遍历。 keys是对键名的遍历,values是对值的遍历,entries是对键值对的遍历。

  for of 默认就是对值的遍历,也就是values的遍历。

  如果不用for of循环,可以手动调用遍历器对象的next方法进行遍历。

8,includes()

  查找数组是否包含给定的值,与字符串的includes类似。

  第二个参数为开始查找的位置。默认为0;支持负数。如果大于数组的长度,则从0开始。

  之前我们都是用indexOf来检查是否包含某个值。 不能比较NaN。

9,数组的空位

  数组的空位指数组的某个位置没有任何值。Array构造函数 返回的数组都是空位。

  空位不是undefined,undefined依然是有值的。

  es5处理空位:  forEach、filter、every、some会跳过空位。map会跳过空位,但会保留这个值。join和toString会将空位视为undefined,undefined和null会被处理为空字符串。

  es6则明确将空位转为undefined。

 

三,对象的扩展

1,属性的简洁表示法

  es6允许直接写入变量和函数作为对象的属性和方法。 允许在对象中只写属性名,不写属性值。属性值等于属性名所代表的变量。

2,属性名表达式

  es6允许字面量定义对象时,表达式作为对象的属性名。即把表达式放在方括号内。(属性名表达式如果是一个对象,则自动转为字符串[object Object])

3,Object.is()

  es5比较两个值是否相等,只有两个运算符:== 和 ===,前者会自动转换数据类型,后者的话NaN不等于自身。以及+0等于-0。

  Object.is()就是用来比较两个值是否严格相等。 Object.is(+0, -0) // false   ;      Object.is(NaN, NaN)   //  true

4,Object.assign()

  将源对象的所有可枚举的属性复制到目标对象。 第一个参数为目标对象,后面的参数都为源对象。

  如果目标对象与源对象有同名属性,或多个源对象有同名属性,则后面的属性会覆盖前面的属性。

  如果只有一个参数,则会直接返回该参数。 

  如果第一个参数不是对象,则会先转成对象,然后返回。 由于undefined和null无法转为对象,所以如果将它们作为参数,就会报错。

  如果undefined和null不是在第一个参数上,则会跳过。

  (注意: Object.assign方法实行的是浅复制。)

   常见用法:

    1,为对象添加属性

      class Point {  constructor (x, y) {  Object.assign(this, {x, y})  }           }

    2,为对象添加方法

      Object.assign (Point.prototype, { fn (){ }  ,  fn1 () { }})

    3, 克隆对象

      Object.assign( { }, obj) ;  这种方法只能克隆原始对象自身的值,不能克隆继承的值。想要保持继承链,

        let originProto = Object.getPorottypeOf( obj );   Object.assign(Object.create( originProto), obj)

    4,合并多个对象

      Object.assign ( target , ... sources);    如果需要返回一个新对象, Object.assign({ }, ...sources);

    5,为属性指定默认值

      Object.assign( {}, DEFAULTS, options) ;

      DEFAULTS对象是默认值,options对象是外部提供的,如果两者有同名属性,则options的属性值会覆盖DEFAULTS的属性。

5,属性的可枚举性

  对象的每一个属性 都具有一个描述对象,用于控制该属性的行为。

  描述对象的enumerable属性称为‘可枚举性’。

  es5的3个操作会忽略enumerable为false的属性:

    for in 循环: 只遍历对象自身的和继承的可枚举的属性

    Object.keys() : 返回对象自身的所有可枚举属性的键名

    JSON.stringify() : 只串行化对象自身的可枚举属性。

  es6的Object.assign() 会忽略enumerable为false的属性,只复制对象自身的可枚举属性。

  es6中所有class的原型的方法都是不可枚举的。

  总结: 操作中引入继承的属性会让问题复杂化,大多时候我们只关心对象自身的属性。所以尽量不要用for in, 而使用Object.keys()代替。

6,属性的遍历

  es6总共有5种方法遍历对象的属性

  1,for in

    循环遍历 对象自身 和继承的可枚举的属性 (不包含symbol属性)

  2,Object.keys()

    返回一个数组,包含对象自身的所有可枚举的属性(不包含symbol属性)

  3,Object.getOwnPropertyNames( obj )

    返回一个数组,包含对象自身的所有属性(包含可枚举的, 不包含symbol属性)

  4,Object.getOwnPropertySymbols(obj)

    返回一个数组,包含对象自身的所有symbol属性

  5,Reflect.ownKeys(obj)

    返回一个数组,包含对象自身的所有属性, (包含可枚举的和symbol属性)

  这5种方法都遵守同样的遍历次序规则

    首先遍历所有属性名为数值的属性,按数字排序。其次遍历所有字符串属性,按照生成时间排序。最后遍历symbol属性,按生成时间遍历。、

7,__proto__, Object.getPrototypeOf ,  Object.setPrototypeOf

  1,__proto__ : 用来读取或设置当前对象的prototype对象。

  2,Object.setPrototypeOf () :  与__proto__相同,用来设置一个对象的prototype对象。

    第一个参数为设置的对象, 第二个参数为设置的原型。

    Object.setPrototypeOf(object, prototype)

  3, Object.getPrototypeOf () :  用来读取一个对象的prototype对象

8,Object.keys() ,  Object.values()、 Object.entries()

  Object.keys 返回该对象本身的所有可枚举属性的键名。

  Object.values 返回该对象本身的所有可枚举属性的键值

  Object.entries 返回该对象本身的所有可枚举属性的键值对的数组。

9,对象的扩展运算符

  1,结构赋值

    从一个对象取值,将所有可遍历的尚未被读取的属性分配到指定的对象上。

    let { x, y, ...z } = {  x:1, y:2 , a: 3, b: 4 }

    z   // { a:3, b:4 }

    解构赋值的复制是浅复制。如果一个键的值是复合类型。那么解构赋值复制的就是这个值的引用。

    解构赋值不会复制继承自原型对象的属性。

  2,扩展运算符  

      ...用于取出参数对象的所有可遍历属性。并将其复制到当前对象中。

    let aClone = {... obj}      =      let aClone = Object.assign( { }, obj);

    上面的只是复制了对象实例的属性,想完整克隆对象还要复制对象原型的属性,有下面两种方法:

    let  aClone = { __proto__ : Object.getPrototypeOf(obj), ...obj};

    let aClone = Object.assign(Object.create(Object.getPrototypeOf(obj)), obj)

    扩展运算符可以用来合并两个对象:

      let ab = { ...a, ...b};     =    let ab = Object.assign({ } , a, b);

    如果自定义的属性放在扩展运算符的后面,则扩展运算符的同名属性将被覆盖。

    扩展运算符的参数对象之中如果有取值函数get,这个函数将会执行。

10, Object.getOwnPropertyDescriptors()

  es5的Object.getOwnPropertyDescriptor方法用来返回某个对象属性的描述对象。

  es6新增了Object.getOwnPropertyDescriptors ,用来返回对象所有本身属性的描述对象的数组。

    

 

四,Symbol

  1,简介

    es6引入了新的原始数据类型Symbol,表示独一无二的值, 它是js的第七种数据类型,前六种分别是: undefined,null,String,Boolean,Number,Object。    

    Symbol函数不能使用new,因为生成的Symbol是一个原始类型的值,不是对象。

    Symbol函数可以接收一个字符串作为参数。 表示对该symbol的描述。

  2,作为属性名

    Symbol作为对象属性名时不能使用点运算符。在对象内部使用Symbol定义属性时,Symbol值必须放在方括号内。

  3, 属性名的遍历

    Symbol会被 Object.getOwnPropertySymbols 遍历出所有的symbol属性名。

    Reflect.ownKeys可以返回所有类型的键名,包含常规的和Symbol键名。

  4,Symbol.for()   ,  Symbol.keyFor()

    

五,set和map

  1,set: 类似数组,但成员的值都是唯一的,没有重复

      数组去重: [ ... new Set(array)];

  2,map: 类似于对象,也是键值对的集合。但键的范围不限于字符串。 各种类型的值都可以当做键。

      

六,promise

1,promise.resolve

  将现有对象转为promise对象。

  如果参数是一个promise实例,则直接返回。

  如果参数是一个thenable对象(具有then方法的对象),会将这个对象转为promise对象,并且立即执行thenable对象的then方法。

  如果参数不是对象,那么返回一个新的promise对象,状态为resolved.

  如果没有参数,则直接返回一个resolved状态的promise对象。

 

 

七,Iterator和for of

  Iterator是一种接口,为各种不同的数据结构提供统一的访问机制。任何数据结构,只要部署Iterator接口,就可以完成遍历操作。

  作用:  1,为各种数据结构提供一个统一的、简便的访问接口。

      2,使得数据结构的成员能够按照某种次序排列。

      3,主要为了for of

  Iterator的本质就是不断的调用当前指针对象的next方法。

2,默认Iterator接口

  es6规定,一个数据结构只要具有 Symbol.iterator属性,就可以认为是'可遍历的';

  原生具备Iterator接口的数据结构如下:

  Array、Map、Set、String、TypedArray、函数的arguments对象, NodeList对象。

3,调用Iterator接口的场合

  1,解构赋值

    对数组和 Set结构进行解构赋值时,会默认调用Symbol.iterator方法。

  2,扩展运算符

    ...也会调用默认的Iterator

    这也就说明,只要部署了Iterator接口,就可以用扩展运算符转为数组。

4,与其他遍历语法的比较

  以数组为例,js提供了多种遍历语法,最原始的写法就是for循环。

  由于比较麻烦,所以数组提供了内置的forEach; 这种写法的问题在于,无法中途跳出循环。

  for  in 循环可以遍历数组的键名。这种方法的缺点有:

    1,数组的键名是数字,但for in 循环是以字符串作为键名。 ‘1’, ‘2’

    2,for in 不仅可以遍历数组键名,还会遍历手动添加的其他键,甚至包括原型链上的键。

    3,某些情况下,for in 循环会以任意顺序遍历键名。

  总之,for in 主要是为了遍历对象设计的。不适应于遍历数组。

  for of与上面的几种方法比较,有一些优点:语法简单,可以配合break、continue使用,提供了遍历所有数据结构的统一操作接口。

 

八,Generator函数的语法

  generator函数是es6提供的一种异步编程解决方案。首先可以把它理解为一个状态机,封装了多个内部状态。 

  执行generator函数会返回一个遍历器对象。也就是说generator函数除了是状态机,还是一个遍历器生成函数。返回的遍历器对象可以依次遍历generator函数内部的每一个状态。 

  形式上,generator函数是一个普通的函数。但有两个特征:一是function命令与函数名之间有一个星号。二是函数体内部使用yield语句定义不同的内部状态。

  调用generator函数返回一个遍历器对象。调用next方法获取下一个 状态。返回一个value和done两个属性的对象。

  1,yield表达式

    只有调用next方法 才会执行yield语句后面的表达式。这为js提供了惰性求值的语法。

    generator函数可以不用yield语句。

    但是yield只能用在generator函数里面。

    如果yield表达式用在另一个表达式中,必须放在圆括号里面。

    

  2,与Iterator接口的关系

    由于generator函数就是遍历器生成函数。因此可以把generator赋值给对象的Symbol.interator属性,从而使得该对象具有Iterator接口。可以被...运算符遍历。

  3,next方法的参数

    yield语句本身没有返回值,总是返回undefined。 next方法可带有一个参数,该参数会被当成上一条yield语句的返回值。

    所以第一次使用next方法时传参是无效的。

  4,for of 循环

    for of可以自动遍历generator函数生成的Iterator对象,不用调用next。

    return 返回的语句不在for of的循环中。

    使用for of可以写出遍历任何对象的方法,原生的js对象没有Iterator接口,无法使用for of,可以通过generator函数为它添加这个接口。 

    除了for of,扩展运算符... ,解构赋值,Array.from()的内部都是遍历器接口。这意味着它们都可以将generator函数返回的Iterator对象当做参数。

  5,错误处理

    Generator函数返回的遍历器对象都有一个throw方法,可以在函数体外抛出错误,然后在函数体内捕获。

    如果Generator函数内部没有布置try catch代码块,那么throw方法抛出的错误将被外部try catch代码块捕获。

    如果Generator函数内部布置了try catch,那么遍历器的throw方法抛出的错误不影响下一次遍历,否则遍历直接终止。

  6,Generator.prototype.return

    generator遍历器还有一个return方法,可以返回指定的值,并终结generator函数的遍历。如果不提供参数,则返回undefined。

    如果generator函数内部有try finally代码块,那么return方法会推迟到finally代码块执行完了再执行。

  7,yield* 表达式

    如果在generator函数内部调用另一个generator函数,默认是没有效果的。

    这时候就需要用到yield*语句。用来在一个generator函数里面执行另一个generator函数。

    yield* 后面的Generator函数(没有return语句时),等同于在Generator函数内部部署了一个for of循环。 在有return语句时,则需要用var value = yield* iterator的形式获取return语句

 

九,Generator函数的异步应用

  1,协程

    多个线程相互协作,但只有一个线程处于运行状态,其他的都处于暂停态,。也就是说一个线程执行到一半,可以暂停执行,将执行权交给另一个线程,等到稍后收回执行权的时候再恢复执行。这种可以并行执行、交换执行权的线程叫 协程。(协程是以多占用内存为代价实现多任务的并行运行。)

  2,协程的Generator函数实现

    Generator函数是协程在es6的实现,最大特点就是可以交出函数的执行权。

    整个Generator函数是一个异步任务的容器。异步操作需要暂停的地方都用yield标明。 next方法的作用是分段执行Generator函数。

  3,Generator函数的数据交换和错误处理

     Generator函数可以暂停执行和恢复执行,这是它能封装异步任务的根本。

    next返回值的value属性是Generator函数向外输出数据。next也可以接收参数,向generator函数体内输入数据。

   4,Thunk函数

    ① 参数的求值策略

      有两张方法, 传值调用和传名调用。 传值调用时在进入函数体之前就把参数计算出来,再将这个值传入函数里。  传名调用是在用到参数的时候再求值参数。传值调用比较简单,但有可能用不到已经求值的参数,造成性能损失。

    ② Thunk函数的含义

      将参数放到一个临时函数中,再将这个临时函数传入到函数中,使用的时候调用临时函数。 这个临时函数就被称为Thunk函数。

    ③ js中的Thunk函数

      因为js语言是传值调用。在js语言中,Thunk函数替换的不是表达式,而是多参数函数,将其替换成一个只接受回调函数作为 参数的单参数函数。

      

十,async函数

  async函数为generator的语法糖。  

  async函数就是将generator函数的*换成了async,将yield换成了await。

  特点:

    1,async函数内置执行器。

      generator函数的执行必须要依靠执行器,所以才有了co模块,而async函数自带执行器。

    2,更好的语义

      async表示函数里有异步操作, await表示紧跟在后面的表达式需要等待结果。

    3,更广的适用性

      co模块约定yield之后只能thunk函数或者promise对象,而async函数的await后可以是promise对象或者原始类型的值(当为原始类型,这时候就等同于同步执行)       

     4,返回值是promise

      async函数返回的是promise对象,这比generator返回的iterator接口更方便。

      进一步说,async函数完全可以看作由多个异步包装的promise对象,而await就是内部then的语法糖。

  用法:

    async函数返回的是一个promise对象,可以使用then方法来添加回调。当函数执行的时候,一旦遇到await就会先返回,等到异步操作完成之后,再接着执行函数体后的语句。

  语法:

    async语法总体比较简单,难的是错误处理机制。

    ①,返回promise对象

      async函数内部return语句返回的值,会成为then方法回调函数的参数。

      

    ②,promise对象的状态变化

      async函数返回的promise对象必须等到内部所有await命令后面的promise对象执行完才会发生状态变化,除非遇到return语句或者抛出错误。

      也就是说只有内部的异步操作执行完,才会执行then方法指定的回调函数。

    ③,await命令

      正常情况下,await命令后面是一个promise对象,如果不是,则会被转为一个立即resolve的promise对象。

      如果await后面的promise对象变为reject状态,则reject的参数会被catch方法的回调接收到。

      只要一个await语句后面的promise变成reject,那么整个async函数都会中断执行。

    ④,错误处理

      如果await后面的异步操作出错,那么等同于async函数返回的promise对象被reject。

      防止出错的办法是将其放在try-catch代码块中,如果有多个await命令,可以统一放在try-catch里面。

    ⑤,使用注意

      1,前面说过,await命令后面的promise对象的运行结果可能是rejected,所以最好把await放在try-catch里面。

      2,如果多个await命令后面的异步操作不存在继发关系,最好让它们同时触发。

           写法1:  let [foo,bar] = await Promise.all ([ getFoo(),  getBar() ]);

         写法2: let fooPromise = getFoo(); let barPromise = getBar();    let foo = fooPromise;   let bar = barPromise;

      3,await命令只能用在async函数中,用在普通函数中就报错。

        如果希望继发执行,则可以使用for循环;

        如果确实希望并发执行,则可以使用promise.all。

 

  async函数的实现原理

    async函数的实现原理就是将generator函数和自动执行器包装在一个函数中。

 

十一: class的基本语法

  1,简介

    es6提供了更接近传统语言的面向对象写法,引入class(类)这个概念作为模板。 

    构造函数的prototype属性在类上继续存在,实际上类的所有方法都定义在类的prototype属性上。这样可以用Object.assign可以很方便的添加多个方法。prototype对象的constructor属性直接指向类的本身。这与es5的行为是一致的。  类的内部定义的方法都是不可枚举的。类的属性名可以采用表达式。

    

  2,constructor方法

    constructor是类的默认方法,通过new命令生成对象实例时自动调用该方法。一个类必须有constructor方法,如果没有显示的定义那么会被默认的添加。

    constructor方法默认返回实例对象,不过也可以指定返回另外一个对象。

    类必须要用new来调用,否则会报错。

  3,类的实例对象。

    与es5一样,实例的属性除非显示定义在其本身上,否则都是定义在原型上。

    类的所有实例共享一个原型对象。 所以可以通过实例的__proto__为原型添加方法。(生产环境,可以使用getPrototypeOf方法来获取实例对象的原型)

    

  4,Class表达式

    采用class表达式可以写出立即执行的Class

  5,不存在变量提升

    类不存在变量提示,因为必须要保证子类在父类之后定义。

  6,私有方法

    es6不提供私有方法,可以模拟实现

     第一种是 在命名上加以区别: 私有方法前面加下划线  _bar () {}

     第二种是  将私有方法放在模块之外。

        第三种是  利用Symbol值的唯一特性将私有方法命名为一个symbol值。

  7,this的指向

    如果单独将类中的方法提取出来使用,那么会导致方法中的this指向错误。

     第一种方法: 可以在构造函数中绑定this

    第二种方法: 使用箭头函数

    第三种方法:使用proxy代理,自动绑定this

  8,name属性

    本质上,class类只是es5构造函数的一层包装,所以很多特性都被类继承;

    name属性总是返回紧跟着class关键字后面的类名。

  9,class的取值函数(getter和setter)

    和es5一样,在类中可以使用get和set关键字对某个属性设置存值函数和取值函数,拦截该属性的存取行为。

    存值函数和取值函数是设置在属性的Descriptor对象上的。

  10,class的Generator方法

    如果某个方法之前加上星号*, 就表示该方法为Generator函数。

  11,class的静态方法

    类想当于实例的原型,里面定义的方法会被所有实例所继承。静态方法就是直接通过类调用,不会被实例继承,也叫做工具方法。

    类中的工具方法就是在一个方法前面加上static关键字。

    父类的静态方法可以被子类继承。

    静态方法也可以从super对象上调用。

  12,class的静态属性和实例属性

    静态属性是指的class本身的属性。通过类直接调用的属性。 只能通过在外部 类.的方法来添加 和修改。

     es6明确指出类只有静态方法,没有静态属性。

  13,new.target属性

    new是从构造函数生成实例的命令。 es6引入了new.target属性,返回new命令所作用的构造函数。 如果构造函数不是通过new命令调用的,则new.target返回undefind, 因此这个属性可以确定构造函数是怎么调用的。

    当子类继承父类时,new.target会返回子类

 

十二: Class的继承

  1,class可以通过extends关键字实现继承。

    子类必须在constructor方法中调用super方法,否则会报错。 这是因为子类中没有this对象,而是继承父类的this对象,然后对其进行加工。如果不调用super,子类中就得不到this对象。

    如果子类没有定义constructor方法,则会被默认添加,super也会默认加在里面。

    在子类的构造函数中,只有调用了super之后,才能使用this,否则会报错。

  2,Object.getPrototypeOf()

    用来从子类上获取父类。

    可以用来判断一个类是否继承了另一个类。  

    (instanceof :   Object instanceof  Class);

  3,super关键字

    super既可以当作函数使用,也可以当做对象使用。

    第一: 当super当做函数调用时,代表父类的构造函数。 es6要求,子类的构造函数必须执行一次super函数。 super虽然代表着父类的构造函数,但返回的却是子类的实例。 就是相当于调用了  父类.prototype.constructor.call(this); 

    第二: 把super当做对象,如果在普通方法中则指向父类的原型对象,在静态方法中则指向父类。

    使用super的时候,必须显示的指定是作为函数还是作为对象使用,否则会报错。

    由于对象总是继承其他对象,所以可以在任意一个对象中使用super关键字。

  4,类的prototype属性和__proto__属性

    class作为构造函数的语法糖,同时有prototype属性和__proto__属性,同时存在两条继承链

    第一条是: 子类的__proto__属性表示构造函数的继承,总指向父类

    第二条是: 子类prototype属性的__proto__ 表示方法的继承,总是指向父类prototype属性。

 

  5,extends的继承目标

    extends后面可以跟多种类型的值。

    class B extends A {}     这里的A可以是任意带有prototype属性的函数。 又因为所有函数都有prototype属性,所以A可以是任意函数。

    有三种特殊情况

    第一种:子类继承Object类    class A extends Object {}

      这种情况下,A其实就是构造函数Object的复制,A的实例就是Object的实例

    第二种: 不存在任何继承  class A {}  

    第三种: 子类继承null     class A extends null {}

  6,实例的__proto__属性

    子实例的__proto__属性的__proto__属性是父实例的__proto__属性。

  7,原生构造函数的继承

    js语言内置了构造函数。大致有以下几种: Boolean()  Number()  String()  Array()  Date()  Function()  RegExp()  Error()  Object()

    以前的这些原生构造函数是无法继承的,因为原生的构造函数会忽略apply方法传入的this,导致拿不到内部属性。

    es6允许继承原生构造函数定义子类。  这也就是说extends不仅可以用来继承类,还可以用来继承原生的构造函数。

    需要注意的是,继承Object的子类有一个行为差异。因为es6规定了Object构造函数的行为,一旦发现不是通过new Object()调用,就会忽略参数。

  8,Mixin模式的实现

    Mixin模式指的是将多个类的接口混入到另一个类中。

    

十三:修饰器

  1,类的修饰

    修饰器decorator是一个函数,用@函数名表示,放在类的前面,修改类的行为。 修饰器函数的第一个参数就是要修饰的目标类。

    如果一个参数不够用,可以在修饰器外再封装一层函数。

  2,方法的修饰

    修饰器不仅可以修饰类,还可以修饰类的属性

    此时,修饰器函数一共可以接收3个参数。 一是要修饰的目标对象,二是要修饰的属性名,三是该属性的描述对象

    如果一个方法有多个修饰器,那么该方法先从外到内进入修饰器,然后由内到外执行。

  3,为什么修饰器不能用于函数

    修饰器只能用于类和类的方法,不能用于函数,因为函数存在函数提升,而类不会提升。

    如果一定要修饰函数,可以采用高阶函数的形式直接执行。

  4,Mixin

    在修饰器的基础上可以实现Mixin模式。 所谓Mixin模式就是对象的混入,意为在一个对象中混入另一个对象的方法。

     

 十四:Module的语法

  1,概述

    es模块的设计思想是尽量静态化,使得编译就能确定模块的依赖关系,以及输入和输出的变量。

    es6模块不是对象,而是通过export命令显示输入代码,再通过import命令输入。

   2,严格模式

    禁止this指向全局对象。 在es6中顶层的this指向undefined。

  3,export命令

    export var a = 1; export var b = 2;

    也可以写成:  var a = 1;  var b = 2;    export {a, b};

    也可以输出函数或类:  export  function a () {}

    也可以使用as关键之重命名;   export {  a as argA, b as argB };

    另外,export的语句输出的接口与其对应的值是动态绑定的,即通过接口可以获取到内部实时的值。

    export模块只能处于模块的顶层。

  4,import命令

    import命令接受一个对象,里面指定要从指定模块导入的变量名, 里面的变量名必须要与导出的名字一样。

    可以使用as将输入的变量重命名。

    import会执行所加载的模块,因此可以直接写 import 'loadsh';   这样写仅仅执行loadsh模块,但不会输出任何值

    如果多次重复执行同一句import,只会执行一次。

  5,模块的整体加载

    除了指定加载某个值,还可以使用整体加载 * 来指定一个对象。

    import * as  obj  from 'a.js';

    模块整体加载所在的对象应该是可以静态分析的,所以不允许运行时改变。

  6,export default命令

    可以使用export default命令为模块指定默认输出

    本质上,export default就是输出一个叫做default的变量或者方法,系统允许我们为它取任意名字。

  7,export和import的复合写法

    如果在一个模块之中先输入后输出同一个模块,import语句可以和export语句写在一起。

    export {  a, b  } from 'module.js';

  8,跨模块常量

    如果想让一个值被多个模块共享,可以单独做一个模块,放置常量,全部导出。 哪里使用就可以直接导入。

 

十五, module的加载实现

  1,传统方法

    默认情况下,浏览器同步加载js,即遇到script就会停下,等到js执行完了再继续。如果是外部的js,还要加入下载js的时间。

    所以浏览器允许js异步加载,可以使用 defer和async属性。

    defer和async的区别是:  defer是渲染完再执行,并且如果有多个脚本,会按照出现的顺序加载。  而async则是下载完就执行,多个async的脚本是不能保证加载顺序的。

  2,加载规则

    es6模块加载要加入type = module属性。默认异步加载。

    在模块中,顶层的this返回undefined,而不是window。利用这个语法点可以检测当前代码是否在es6模块中。

  3,es6模块和commonjs的差别

    commonjs输出的是一个值的复制,而es6则输出的是值的引用

    commonjs是运行时加载,而es6则是编译时输出接口

  4,node加载

    在node中,将commonjs和es6模块是分开的,采用各自的加载方式

    在静态分析阶段, 一个模块脚本只要有一行import或者export,就会被当做es6模块。否则就当做commjs模块。

    如果不使用export和import也希望被node认为是es6模块,可以在脚本中加入  export { };  这个命令的意思是不输出任何接口的es6标准写法。

    es6模块和commonjs的另一个重大差异是:  es6模块中,顶层this指向undefined, commonjs中 顶层this指向当前模块。

 

十六: 编程风格

  1,let和const

    建议用let取代var,  在let和const之间,优先选择const,尤其是在全局环境中,尽量不设置变量,只应设置常量。

    const优于let的几个原因:

      const可以提醒阅读的人,这个变量不应该被改变

      const比较符合函数式编程思想,运算不改变值,只是新建值。

      js编译器会对const进行优化。

  2,字符串

    静态字符串一律使用单引号或者反引号,不使用双引号。  动态字符串使用反引号。

  3,解构赋值

    使用数组成员对变量赋值时,优先使用解构赋值。

    函数的参数如果是对象的成员,优先使用解构赋值。

    如果函数返回多个值,优先使用对象解构赋值,而不是数组的解构赋值。

  4,对象

    单行定义的对象,最后一个成员不以逗号结尾。 多行的对象,最后一个成员以逗号结尾。

    对象尽量静态化,少添加新的属性,如果添加属性不可避免,要使用Object.assign方法。

    如果对象的属性名是动态的,可以在创建对象的时候使用属性表达式定义。

  5,数组

    使用扩展运算符(...)复制数组。

    使用Array.from方法将类数组转为数组。

  6,函数

    立即执行函数可以写成箭头函数的形式。

    简单的、单行的、不会复用的函数,建议采用箭头函数,如果函数比较复杂,则还是应该采用传统的函数。

    所有的配置项都应该集中在一个对象,放在最后一个参数,布尔值不可以直接作为参数。

    不要在函数体内使用arguments对象,使用rest运算符。

    使用默认值语法设置函数参数的默认值。

  7,Map结构

    注意区分map和object: 只有模拟实体对象时才使用object,如果只是需要key:value的数据结构,则使用map。因为map有内建的遍历机制。

  8,class

    总是用class取代需要prototype的操作。

    使用extends实现继承,这样更简单,也不存在破坏instanceof运算的危险。

  9,模块

    Module是js模块的标准写法,坚持使用;

    不要同时使用export default 和普通的export。

    不要在模块输入中使用通配符,这样可以确保模块中有一个默认的输出。

    如果模块默认输出一个函数,则函数名首字母小写。 如果默认输出一个对象,则对象首字母应该大写

    

十七: 二进制数组的应用

  1,ajax

    传统上,服务器通过ajax操作只能返回文本数据,即responseType默认属性为text。 xhr2允许服务器返回二进制数据。这分两种情况: 如果明确知道返回的二进制数据类型,可以把返回类型(responseType)设置为arraybuffer,如果不知道,就设置为blob。

  2,canvas

    网页canvas元素输出的二进制像素数据就是TypedArray数组。

  3,websocket

    websocket可以通过ArrayBuffer发送或者接收二进制数据。 

  4,fetch API

    fetch API取回的数据就是ArrayBuffer对象。

  5,File API

    如果知道一个文件的二进制数据类型,也可以将这个文件读取为ArrayBuffer对象。

  

posted @ 2019-02-15 12:02  ken丶123  阅读(166)  评论(0编辑  收藏  举报