一, 函数参数的默认值
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对象。