• 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对象。

      

  • 相关阅读:
    js禁止页面回退,刷新,右键代码
    asp.net网站中的Gridview循环判断数据是否被选中
    Gridview中同时选中并删除多个数据
    asp.net中在后台更换控件图片的代码
    关于gcd的8题
    flash AIR 通过BitmapData生成图片到android Camera相册
    flash AIR 通过BitmapData生成图片到本地
    C的随机数
    xcode svn设置事项
    拒绝session丢失 利用DIV层实现对模态窗口的模拟
  • 原文地址:https://www.cnblogs.com/wjyz/p/10382943.html
Copyright © 2020-2023  润新知