1 Star 0 Fork 0

zweekend/javascript-lang-notes

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
该仓库未声明开源许可证文件(LICENSE),使用请关注具体项目描述及其代码上游依赖。
克隆/下载
es6+.txt 49.46 KB
一键复制 编辑 原始数据 按行查看 历史
zweekend 提交于 2018-08-30 17:21 . update Object.md
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942
一、引用数据类型
1、Object类型
(1)创建实例对象
对象字面量方式==>属性或方法的简写、属性名或方法名的表达式
(2)方法的name属性
(2)新增API
Object.is()
Object.assign()
Object.getOwnPropertySymbols()
Reflect.ownKeys()
Reflect.enumerate()
Object.prototype._proto_ ==>Object.prototype[[prototype]]
Object.setPrototypeOf()
(3)扩展运算符应用于对象
2、Map类型
Map类型和Object类型相似,特点是可以用任何类型的数据来作为键名
(1)创建实例对象
let k1={name:"zw"};
let k2={age:24};
let m=new Map();
m.set(k1,"111");
m.set(k2,"222");
let m=new Map([[k1,"111"],[k2,"222"]]);
(2)Map类型的API
map.size
map.set()
map.get()
map.delete()
map.has()
map.delete()
map.clear()
map.keys()
map.values()
map.entries() ==>map[Symbol.iterator]
map.forEach()
3、WeakMap类型
WeakMap类型和Map类型类似,区别就是:键名只能是对象,且对象是弱引用(不可遍历)
(1)创建实例对象
let k1={name:"zw"};
let k2={age:24};
let wm=new WeakMap();
wm.set(k1,"111");
wm.set(k2,"222");
let wm=new WeakMap([[k1,"111"],[k2,"222"]]);
(2)WeakMap类型的API
wm.set()
wm.get()
wm.has()
wm.delete()
4、Array类型
(1)新增API
Array.from()
Array.of()
arr.copyWithin()
arr.fill()
arr.includes() ==>indexOf()
arr.find()
arr.findIndex()
arr.keys()
arr.values() ==>arr[Symbol.iterator]
arr.entries()
(3)数组的空位
--空位不等于undefined,undefined仍然有值,而空位是什么都没有
--ES5大多数情况下都会忽略空位,而ES6会明确将空位转为undefined
(4)数组的推导
--直接通过现有数组生成新数组
--for..of结构(可使用多个)+if条件语句(可使用多个)+返回的表达式
--数组推导可用来代替map()和filter()方法
5、Set类型
Set类型是一种类似于数组的新的数据结构,其特点是成员值都是唯一的,没有重复的值,而且键、值一样
(1)创建实例对象
let s=new Set();
[1,2,3,4].map(item=>s.add(item));
let s=new Set([1,2,3,4]);
(2)Set类型的API
set.size
set.add()
set.delete()
set.has()
set.clear()
set.keys()
set.values() ==>set[Symbol.iterator]
set.entries()
set.forEach()
6、WeakSet类型
WeakSet类型和Set类型类似,但是有两点区别:WeakSet类型的成员只能是对象,对象都是弱引用(所以无法引用WeakSet成员,且不可变遍历)
(1)创建实例对象
let ws=new WeakSet()
ws.add([1,2]).add([3,4]);
let ws=new WeakSet([[1,2],[3,4]);
(2)WeakSet类型的API
ws.add()
ws.delete()
ws.has()
7、Date类型
8、RegExp类型
(1)正则表达式的转义字符增加了\u{xxxx}
(2)创建实例对象
var reg=/pattern/flag;
flag增加了u修饰符、y修饰符
var reg=new RegExp("pattern","flag"); ==> var reg=new RegExp(pattern,"flag");
(3)u修饰符
(4)y修饰符
(5)新增API
reg.sticky
reg.flags
reg[Symbol.search]() ==>str.search()
reg[Symbol.match]() ==>str.macth()
reg[Symbol.replace]() ==>str.replace()
reg[Symbol.split]() ==>str.split()
RegExp.escape()
9、string基本数据类型和String类型
(1)string基本数据类型
ES6在转义字符方面,进行了加强,以此来便利的表示码点大于0xFFFF的字符,如下所示:
Unicode编码字符集(码点) 0x0000—0xFFFF >0xFFFF
-----------------------------------------------------------------------------------------------------
UTF-16编码 0x0000—0xFFFF 0xnnnn0xnnnn
(存储在计算机上的编码值) (和码点相同,但必须用2个字节存储) (由码点转换而来,必须用4个字节存储)
-----------------------------------------------------------------------------------------------------
'\z'==='z'
'\172'==='z'
JavaScript的字符转义方式 '\x7A'==='z'
'u007A'==='z' '\unnnn\unnnn' -->直接对UTF-16编码转义
'\u{7A}'==='z' '\u{>0xFFFF}' -->直接对码点转义
(2)String类型新增的API
str.at() ==> str.charAt()
str.codePointAt() ==> str.charCodeAt()
String.fromCodePoint() ==> String.fromCharCode()
str.includes()
str.startsWith()
str.endsWith()
str.repeat()
str.padStart()
str.padEnd()
str.normalize()
(3)模板字符串与“标签模板”功能
String.raw()
10、number基本数据类型和Number类型
(1)number基本数据类型
二进制数前缀:0b、0B
八进制数前缀:0o、0O
十六进制数前缀:0x、0X
(2)Number类型新增API
Number.isFinite() ==> isFinite()
Number.isNaN() ==> isNaN()
Number.parseInt() ==> parseInt()
Number.parseFloat() ==> parseFloat()
Number.isInteger()
Number.MAX_SAFE_INTEGER
Number.MIN_SAFE_INTEGER
Number.isSafeInteger()
Number.EPSILON
(3)指数运算符
a**2=a*a
a**3=a*a*a
11、Boolean类型
12、Function类型
(1)函数参数的默认值
与解构赋值默认值结合使用
参数默认值位置(至于最后一个参数)
length失真
默认值为变量时,变量的作用域
(2)rest参数
(3)name属性
(4)箭头函数
(5)函数绑定
(6)尾调用优化(尾递归)
(7)函数参数的尾逗号
13、Error类型
14、Promise类型
15、单体内置对象
(1)Golbal对象
(2)Math对象
ES6新增17个与数学相关的方法
(3)JSON对象
(4)Symbol对象
通过函数Symbol可以创建Symbol基本数据类型的值,如下:
let s1=Symbol();
let s2=Symbol();
console.log(s1); //Symbol()
console.log(s2); //Symbol()
console.log(s1===s2); //fasle
let s3=Symbol("foo");
let s4=Symbol("foo");
console.log(s3); //Symbol(foo)
console.log(s4); //Symbol(foo)
console.log(s3===s4); //false
let s5=Symbol.for("foo");
let s6=Symbol.for("foo");
console.log(s5); //Symbol(foo)
console.log(s6); //Symbol(foo)
console.log(s5===s6); //true
(5)Reflect对象
Reflect内置对象设计的目的有如下几点:
1)将Object对象的一些明显属于语言层面的方法放到Reflect对象上,并修改某些Object方法的返回结果
2)将Object.prototype和Function.prototype上的一些方法放到Reflect对象上
3)让Object、Function操作都变成函数行为
4)Reflect对象总的来说就是对Object、Function的方法以及操作的默认行为进行了封装完善以及函数行为化,
而Proxy是用于修改上述部分方法和操作的默认行为的,因此可以方便的在Proxy对象中调用对应的Reflect方法来完成默认行为
Reflect对象的API
1) 迁移Object对象上属于语言层面的方法,并修改某些方法的返回结果
Reflect.defineProperty()--返回值为布尔值,表示属性设置是否成功 ==> Object.defineProperty()--返回值是原对象或抛出错误
Reflect.getOwnPropertyDescriptor() ==> Object.getOwnPropertyDescript()
Reflect.keys() ==> Object.keys()
Reflect.getOwnPropertyNames() ==> Object.getOwnPropertyNames()
Reflect.getPrototypeOf() ==> Object.getPrototypeOf()
Reflect.setPrototypeOf() ==> Object.setPrototypeOf()
Reflect.freeze()--返回值是布尔值,表示对象冻结是否成功 ==> Object.freeze()--返回值是原对象或抛出错误
Reflect.isFrozen() ==> Object.isFrozen()
Reflect.seal()--返回值是布尔值,表示对象的密封是否成功 ==> Object.seal()--返回值是源对象或抛出错误
Reflect.isSealed() ==> Object.isSealed()
Reflect.preventExtensions()--返回值是布尔值,表示阻止对象扩展是否成功 ==> Object.preventExtensions()--返回值是原对象或抛出错误
Reflect.isExtensible() ==> Object.isExtensible()
2) 迁移Object.prototype和Function.prototype上的一些方法
Reflect.hasOwn() ==> Object.prototype.hasOwnProperty()
Reflect.apply() ==> Function.prototype.apply(obj,arg)
3) 让Object、Function操作都变成函数行为
Reflect.get() ==> obj.property操作
Reflect.set()--返回一个布尔值,表示属性值设置是否成功 ==> obj.property=value操作
Reflect.has() ==> property in obj
Reflect.enumerate() ==> for(let key in obj)
Reflect.deleteProperty() ==> delete obj.property
Reflect.construct() ==> new fun()
16、Proxy类型
(1)创建实例对象
var proxy=new Proxy(target,handler);
--Proxy实例对象proxy,其实就是target的一个副本,包裹着'拦截层',因此proxy和target是同类型,而并不是Proxy类型,即 proxy instanceof Proxy //false
--要起到Proxy作用,必须对实例proxy进行操作,而不是针对目标对象
--如果handler没有设置相应的拦截,则等同于直接操作目标对象
--实例proxy可以作为其他对象的原型对象
--技巧:target.proxy=new Proxy(target,handler);
var {proxy,revoke}=Proxy.revocable(target,handler);
(2)handler对象中可设置的拦截操作
var handler={
get(){},
set(){},
has(){},
hasOwn(){},
deleteProperty(){},
enumerate(){},
ownKeys(){},
defineProperty(){},
getOwnPropertyDescriptor(){},
preventExtensions(){},
isExtensible(){},
getPrototypeOf(){},
setPrototypeOf(){},
apply(){},
constructor(){}
}
17、Iterator和for..of循环
JavaScript中表示“集合”的数据结构有很多,包括:Object、Map、Array、Set、类似数组对象(如字符串、DOM NodeList对象、arguments对象),但是不同的
数据结构有着或相同或不同的遍历成员的方法,因此ES6为这些数据结构提出了一种统一的遍历机制,即遍历器机制,其最重要的作用为:
*****用于和数据结构建立关系,然后通过遍历器对象,遍历出数据结构的成员,以完成一些特殊的需求、功能*****
(1)遍历器机制
遍历器机制的两个核心要素便是:遍历器生成函数(iterable)、遍历器对象(iterator)
--遍历器生成函数,是一个接口,用来返回一个遍历器对象;
--遍历器对象,其本质是一个指针对象,是对数据结构执行遍历操作的实体,主要通过其next()方法实现遍历,遍历的过程如下:
• 第一次调用遍历器对象的next()方法,将指针指向数据结构的第一个成员
• 第二次调用遍历器对象的next()方法,将指针指向数据结构的第二个成员
• 不断调用遍历器对象的next()方法,直到指向数据结构的结束位置
• 每一次调用next()方法,都会返回数据结构的当前成员的信息,即一个对象
{
value://当前成员的值
done://表示遍历是否结束的布尔值
}
--遍历器机制中,只是将遍历器生成函数部署在数据结构上,因此遍历器对象与所遍历的数据结构实际上是分开的,没有必然的联系,具体表现如下:
•完全可以写出没有对应数据结构的遍历器对象,或者说用遍历器对象模拟出一个数据结构
•对于如何去遍历作为载体的数据结构,以及遍历数据结构的哪些成员,完全取决于对遍历器生成函数的设置
(2)为数据结构部署遍历器接口
1)默认部署遍历器接口
ES6中,为部分数据结构默认的部署了遍历器接口(遍历器生成函数),即作为Symbol.iterator、keys、values、entries等属性的属性值而存在,这些数据结构
包括:
• Array
• 类数组对象(字符串\arguements对象\DOM NodeList对象)
• Set和Map结构
2)手动部署遍历器接口
ES6中,Object(包括其他的一些类数组对象)均没有部署默认的遍历器接口,需要手动的部署,但需要注意的是,一般将遍历器接口部署在数据结构的Symbol.iterator
属性上,但并非意味着只能将遍历器接口部署在Symbol.iterator,正如ES6为部分数据结构默认部署的遍历器接口一样,同样在keys、values、entries等属性上部署了用于
遍历数据结构不同成员的遍历器接口,只是Symbol.iterator属性上部署的遍历器接口会作为一些遍历操作默认调用的方法,而keys等方法需要手动调用,一Symbol.iterator
属性为例,手动部署遍历器接口如下所示:
1)Object
支持ES6的环境中,Object对象部署遍历器接口不是很必要,因为这时对象实际上被当做Map结构使用,不过在支持ES5的环境中,可以进行手动部署,方法如下:
2)某些类数组的对象
这些类数组对象必须具有数值键名和length属性,
• 其Symbol.iteartor属性可以直接引用数组的接口,即:likeArray[Symbol.iterator]=Array.prototype[Symbol.iterator]
• 直接将类似数组的对象转为数组
(3)调用遍历器对象遍历数据结构的方式
1)手动调用
手动调用主要是调用数据结构上部署了遍历器接口的方法,生成相应的遍历器函数,然后用while语句去循环调用遍历器对象的next()方法,并对next()方法每次返回
的结果进行处理以完成相应的功能,以Symbol.iterator方法为例,手动调用如下所示:
var $iteartor=ITERABLE[Symbol.iterator]();
var $result=$iterator.next();
while(!$result.done){
var val=$result.value;
$result=$iterator.next();
return val;
}
2)自动调用
与手动调用相较而言,自动调用需要特定的操作才能执行,而且相较于手动调用的灵活性,自动调用,默认会调用(且只能调用)数据结构的Symbol.iterator方法,
因此,若数据结构的Symbol.iterator属性上未部署遍历器接口,则无法实现自动调用,自动调用的操作包括:
#1 for...of循环
for...of循环的本质相当于对手动调用中对while语句的封装,每次返回的结果供在for...of循环的外部使用,也就是说for...of循环直接的作用对象是遍历器对象,
和数据结构没有必然的联系,正如遍历器接口、遍历器对象和数据结构没有必然联系一样,因此for...of的应用场合包括:
>> 直接作用于遍历器对象
for...of循环直接作用于遍历器对象时,其内部执行和while语句相似的功能,适用于任何的遍历器对象,常见的用于数组、Set、Map数据结构的keys、values、
entries方法返回的遍历器对象,用来遍历数据结构不同的成员
>> 直接作用于数据结构
for...of循环直接作用于数据结构时,其内部会默认先调用数据结构的Symbol.iterator方法,再用for...of循环直接作用于生成的遍历器对象,常见的直接用于
遍历数组、类数组对象(字符串\arguements\DOM NodeList对象)、Set和Map结构、Object对象(部署了Symbol.iterator方法)
#2 扩展运算符
扩展运算符的本质就是在内部调用for...of循环,只是限定了for...of循环外部对返回结果的处理方式,即将每次返回的结果拼接成一个参数序列,因此,扩展运算符
直接的作用对象是遍历器对象,和数据结构没有必然的联系,其应用场合包括:
>> 直接作用于遍历器对象
可以用于generator函数
>> 直接作用于数据结构
扩展运算符直接作用于数据结构时,会默认先调用数据结构的Symbol.iterator方法,然后就是执行for...of循环,如下列应用:
• 等效于Array.from()方法,用于将一些具有iterator接口的数据接口转化为数组(如字符串、Map和Set结构、Generator函数以及一些类数组对象)
• 合并数组
• 与数组变量结合,在解构赋值中生成数组
• 与数组变量结合,作为函数的rest参数
• 与数组实例结合,在函数调用时为其传递参数(此种情形可以代替数组的apply方法,将数组转为函数的参数)
#3 变量的解构赋值
在采用数组形式为变量进行解构赋值时,和扩展运算符相似,其本质就是在内部调用for...of循环,也只是限定了for...of循环外部对返回结果的处理方式,即将
每次返回的结果按位置排列好以后,再赋值给等式左边相同位置的变量,因此,采用数组形式为变量进行解构赋值时,直接作用的对象是遍历器对象,和数据结构没有必然
的联系,其应用场合包括:
>> 直接作用于遍历器对象
let arr=[1,2,3];
let iterator=arr[Symbol.iterator]();
let [x,y,z]=iterator;
console.log(`x:${x} y:${y} z:${z}`); //x:1 y:2 z:3
>> 直接作用于数据结构
等号的右边直接使用数据结构时,会默认先调用数据结构的Symbol.iterator方法,然后执行for...of循环
let arr=[1,2,3];
let [x,y,z]=arr;
console.log(`x:${x} y:${y} z:${z}`); //x:1 y:2 z:3
#4 yield*
#5 作为参数传递
某些向函数传递参数的操作,其本质就是在内部对参数调用for...of循环,只是限定了for...of循环外部对返回结果的处理方式(不同场景不同对待),因此,
这些传递参数的操作,直接作用的对象是遍历器对象,和数据结构没有必然的联系,其应用场景包括:
>> 直接传递遍历器对象
Array.form()
Map()、WeakMap()、Set()、WeakSet()
Promise.all()
Promise.race()
>> 直接传递数据结构
当直接传递数据结构时,会默认调用数据结构的Symbol.iterator方法,然后执行for...of循环
Array.form()
Map()、WeakMap()、Set()、WeakSet()
Promise.all()
Promise.race()
二、面向对象的程序设计
(1)Class创建自定义对象
ES5传统写法(构造函数+原型):
function Point(x,y){
this.x=x;
this.y=y;
}
Point.prototype.toString=function(){
return "("+this.x+","+this.y+")";
};
ES6的写法(Class):
class Point{
constructor(x,y){
this.x=x;
this.y=y;
}
toString(){
return "("+this.x+","+this.y+")";
}
}
对Class写法的剖析:
1)Class写法完全是传统构造函数+原型的另一种写法,本质还是一样
2)Class写法注意的点:定义“类”的方法时,不需要加上function关键字;
方法之间不需要用逗号分隔(不是对象字面量形式)
3)class关键字==function关键字,,所以class关键字可以看做是声明函数的另一种方式,“类”也就是“构造函数”如下所示:
class Point{} ==> function Point(){}
let fun=class {} ==> let fun=function(){}
let fun=class f{} ==> let fun=function f(){}
console.log(fun.name) //f console.log(fun.name) //f
但是区别在于:用function声明的构造函数在不使用new调用时,和普通函数没有区别,但是用class声明的类(构造函数),在不用new调用时,就会报错
用function声明的构造函数存在函数声明提升,但是用class声明的类(构造函数),不存在声明提升
4)在“类”(构造函数)内部定义的方法,在形式上就等同在构造函数内部声明函数,只是不需要用function关键字而已,如下:
class Point{ function Point(){
toString(){ function toString(){
return "("+this.x+","+this.y+")"; ==> return return "("+this.x+","+this.y+")";
} }
} }
5)在“类”(构造函数)内部定义的方法,其实本质上都是在“类”(构造函数)的prototype属性上定义方法,如下:
class Point{ function Point(){}
constructor(x,y){ Point.prototype.constructor=function(x,y){
this.x=x; this.x=x;
this.y=y; this.y=y;
} }
toString(){ ==> Point.prototype.toString=function(){
return "("+this.x+","+this.y+")"; return "("+this.x+","+this.y+")";
} }
} Point=Point.prototype.constructor; //ES5中是让constructor指向Point,此处颠倒
因此,其内部的本质和ES5大致一样,比较如下:
相同点:
ES5中,Point.prototype的constructor属性是默认的,此处也是默认的,即如果不在“类”(构造函数)中定义,也会默认存在
不同点:在“类”(构造函数)内部定义的方法,虽然是Point.prototype的实例属性,但是也是不可枚举的,如下:
ES6:Object.keys(Point.prototype) //[]
Object.getOwnPropertyNames(Point.prototype) //["constructor","toString"]
ES5: Object.keys(Point.prototype) //["toString"]
Object.getOwnPropertyNames(Point.prototype) //["constructor","toString"]
6)利用new操作符创建实例对象时,和ES5完全一样,会去自动调用“类”(构造函数)
(2)Class继承
ES5传统继承(原型链+借助构造函数技术):
function Super(x,y){
this.x=x;
this.y=y;
}
function Sub(x,y,z){
Super.apply(this,[x,y]); //借助构造函数技术实现实例属性的继承
this.z=z;
}
Sub.prototype=new Super(x,y); //借助原型链实现共享属性的继承
ES6class继承:
class Super{
constructor(x,y){
this.x=x;
this.y=y;
}
toString(){
return "("+this.x+","+this.y+")";
}
}
class Sub extends Super{
constructor(x,y,z){
super(x,y);
this.z=z;
}
toString(){
return this.z+" "+super.toString();
}
}
class继承剖析:
1)关键字extends,其实完成了如下几件事:
•设置“类”Sub的prototype属性,在内部实现了原型链继承,即Sub.prototype=new Super(x,y); ==> 完成了ES5的原型链继承
•修改了“类”Sub的_proto_属性,即Sub._proto_=Super; ==> 这是和ES5的一个区别
•使得自定义的“类”可以继承原生的构造函数,如Object、Array等 ==> ES5无法继承原生构造函数
2)关键字super
•必须在Sub的constructor方法内部最开始的地方调用super(x,y) ==>完成了ES5的借助构造函数继承
若Sub的内部没有显式的定义constructor方法,会默认有一个constructor方法,且constructor内部会默认调用super(...args)
若Sub的内部有显式定义constructor方法,则不可省略super(...args),且必须置于最开始,否则后续代码无法访问到this
注:至于为什么必须放在constructor方法的最开始的地方,待进一步探究
•在Sub内任何其他地方,只能以super.xxx的形式来使用super关键字,此时相当于调用Super.prototype.xxx
总而言之:class的继承依靠extends和super两个关键字,在内部实现了ES5的传统原型链继承以及借助构造函数技术继承,而且使得子类构造函数本身也继承父类构造函数,
这是很大的一个区别,再者就是能够实现对原生构造函数的继承
(3)class的静态方法和静态属性
ES5实现静态方法和静态属性:
function Fun(){
}
Fun.toString=function(){ //实现静态方法
return "hello";
}
Fun.title="wrold"; //实现静态方法
ES6实现静态方法和静态属性:
class Fun{
static toString(){ //利用static关键字实现静态方法
return "hello";
}
static title="wrold"; //ES7中利用static实现静态方法
}
Fun.title="wrold"; //和ES5实现静态属性一样
(4)new.target属性
ES6为new操作符提供了一个target属性,ES5也适用只能在类(构造函数)内部使用,返回new操作符所作用的“类”(构造函数)
三、异步编程
所谓的异步,就是将一个任务分成两个阶段,先执行第一个阶段,然后转而执行其他任务,等第一个阶段返回结果时,根据返回的结果再回过头执行第二个阶段,因此,
不连续的执行叫做异步,而连续的执行就叫做同步
因为JavaScript引擎是单线程的,即在主线程上,所有的任务都是同步执行的,但是浏览器是多线程的,借助于浏览器定时触发线程、浏览器HTTP异步请求线程、浏览器事件触发线程,
以及消息队列以及Event loop,便可实现JavaScript的异步编程,其原理为:
所有的工作线程用来和主线程并发的执行异步任务的第一个阶段,因此可以不阻塞主线程的同步执行,当工作线程执行完毕后,返回结果,并将作为异步任务第二个阶段的回调函数推送到
消息队列中,通过Event Loop机制,当主线程上的同步任务执行完毕后,就会读取消息队列中的任务来执行,最终实现异步编程
注:异步和阻塞没有必然的联系,消息队列的存在虽然解决了异步编程的问题,但是消息队列本身是一个队列,因此各消息之间也存在着阻塞的问题
JavaScript异步编程的实现主要有以下几个方法:
1、回调函数
所谓的回调函数,指的是将函数B作为参数,传递给函数A,函数A的内部适时的调用函数B,则函数B就称为回调函数,常见的就是将函数作为参数传递给API。但是,回调函数和
异步没有必然的联系,传入的函数也可能被同步执行,因此,是同步回调函数,还是异步回调函数,取决于回调函数处理的是同步任务还是异步任务,即回调函数扮演的角色
典型的异步回调函数就是setTimeout()中的函数参数,因为定时器本身就是一个异步任务,会在一定的事件间隔后处理作为参数传递进来的回调函数
2、事件监听器模式(发布/订阅模式)
事件监听器模式是一种广泛用于异步编程的模式,是回调函数的事件化,也称为发布/订阅模式
3、Promise对象
无论是回调函数,亦或是事件监听器模式,其本身并没有任何问题,问题出现在多个回调函数的嵌套,使得代码不是纵向发展,而是横向发展,造成‘回调地狱’(callback hell)
,无法管理
Promise对象便解决了callback hell问题,使得横向加载的回调函数改成纵向加载
(1)Promise对象的基本用法以及工作原理
1)基本使用
let promise=new Promise(function(resolve,reject){
//异步操作的代码
if(/*异步操作成功*/){
resolve(value);
}else{
reject(error);
}
});
promise.then(function(value){
console.log(value);
},function(error){
console.log(error);
});
2)工作原理
Promise对象是一个构造函数,接收一个函数作为参数,用来生成Promise实例,通过Promise实例的状态的改变,来调用部署在Promise实例API上的相应回调函数,
从而完成异步操作
>> 作为参数的函数
Promise对象接收一个函数作为参数,其作用如下:
• 函数内部对异步操作进行封装(异步任务的第一个阶段)
• 函数有两个固定参数分别是resolve和reject函数(由javaScript引擎提供,不需要自己部署),通过调用两个函数,来改变Promise实例的状态,并将异步操作
的结果传递出去
>> 作为状态机的Promise实例
通过new + Promise构造函数生成的Promise实例,其本质上是一个状态机,有3种状态:
• Pending(进行中)
• Resolved/Fulfilled(已完成)
• Rejected(已失败)
Promise实例的状态有以下3个特点:
• 状态不受外界影响
通过上述调用resolve和reject函数,可以改变Promise实例的状态,任何外界的操作都不可以改变其状态,具体原理如下:
resolve:一般在异步操作成功时调用,用来将Promise实例的状态由Pending变为Resolved
resolve函数接收参数,正常为异步操作的结果,并将结果传递出去,但也可以是另一个Promise实例,此时Promise实例的状态由作为参数的Promise实例的状态决定
reject: 一般在异步操作失败时调用,用来将Promise类型对象的状态由Pending变为Rejected(Promise类型对象的状态变为Rejected也可以不通过调用reject函数,只要异步
任务失败抛出错误,甚至是我们自己抛出一个错误,都可以将状态变为Rejected,当然手动抛出错误必须保证之前没有调用resolve函数)
reject函数接收参数,通常为Error类型的实例,表示异步操作抛出的错误并传递出去
•Promise实例的状态变化只有两种可能:从Pending变为Resolved、从Pending变为Rejected
•一旦状态改变就不会再变,任何时候都可以得到这个结果
>>部署在Promise实例API上的回调函数
Promise实例可以通过then()和catch()方法,部署相应的回调函数,以供Promise实例的状态发生改变时调用,来完成异步任务的第二个阶段
>>总结
Promise实例通过将异步任务的第一阶段进行封装,然后根据异步操作的结果来改变自身的状态并保存异步操作返回的结果,最后借助统一的API,以同步操作的流程
来调用部署在API上的相应回调函数,使得异步任务的两段执行看的更清楚,避免了层层嵌套的回调函数,解决了callback hell的问题
(2) Promise实例的缺点
•无法取消Promise,一旦新建就会执行,无法中途取消
•当处于Pending状态时,无法得知目前进展到哪个阶段
•如果不设置回调函数,Promise内部抛出的错误就不会反应到外部
•Promise的写法只是回调函数的改进,一堆的then使得代码冗余,也使得语义变的不清楚
(2)Promise对象、Promise实例的API
Promise.prototype.then() ==> promise.then()
Promise.prototype.catch() ==> promise.catch()
* Promise.prototype.done()
* Promise.prototype.finally()
Promise.resolve()
Promise.reject()
Promise.all()
Promise.race()
(3)Promise对象的应用
1)加载图片
2)与Generator函数结合
4、Generator函数
(1) Generator函数的语法
Generator函数仍然是一个普通函数,但是和普通函数有着很多的区别,其语法与传统的函数完全不同,如下所示:
1)书写形式
传统函数
function foo(x,y){...}
Generator函数
function* foo(x,y){...}
注:调用方式相同,均为foo()
2)yiled语句&状态机
Generator函数可以看做是一个状态机,函数体内使用yield语句定义不同的内部状态,这些内部状态赋予了Generator函数可以暂停执行和
恢复执行的功能,即可以交出和取得函数的执行权,如下所示:
function* foo(){
yield 'hello';
yield 'wrold';
return 'ending';
}
3)遍历器生成函数
Generator函数除了是状态机,还是一个遍历器生成函数,调用Generator函数之后,函数并不会执行,而是返回一个遍历器对象,用来遍历
Generator内部的每一个状态,因此配合着yiled语句控制函数执行权的功能,使得Generator函数可以分段执行,完成和传统函数同样的功能,具体
原理如下所示:
function* foo(){
console.log('hello');
yield 'step1';
console.log('wrold');
yield 'step2';
console.log('Javascript');
return 'ending';
}
let g=foo();
//返回一个遍历器对象,并不执行函数
g.next();
//'hello'
//{value:'step1',done:false}
g.next()
//'wrold'
//{value:'step2',done:false}
g.next()
//'Javascript'
//{value:'ending',done:true}
g.next()
//{value:undefined,done:true}
总结一下遍历器对象配合yiled语句分段执行Generator函数的逻辑,如下所示:
• 首先调用Generator函数,生成相应的遍历器对象,并不执行函数
• 调用遍历器对象的next()方法,开始执行函数,直到遇到yield语句(即遍历到一个状态),暂停执行函数,并将紧跟在yield后的表达式的
值作为返回的对象的value属性值
• 继续调用next()方法,从函数上一次暂停的地方继续执行函数,直到遇到下一个yield语句(即遍历到一个状态),依然暂停执行函数,并将紧跟在
yield后的表达式的值作为返回的对象的value属性值
• 如果没有再遇到新的yield语句,就一直执行到函数结束,直到遇到return语句为止,并将return语句后面的表达式的值作为返回的对象的value属性值
• 如果该函数没有return 语句,则返回的对象的value属性值为undefined
注:> yield后的表达式,是‘惰性求值’,即只有当遍历器对象通过next()方法遍历到该yield语句时,才会求值
> yield语句和return语句的区别为,每次遇到yield语句函数会暂停执行,下一次会从该位置继续向后执行,而return语句不具有位置记忆功能,
一个Generator函数只能执行一次return语句,但却可以执行多条yield语句
> 传统函数只能借助return语句返回一个值,而Generator可以借助yield语句返回多个值
> Generator可以不用yield语句,这时就变成一个单纯的暂缓执行函数
> yield语句不能用在普通函数中,否则会报错
> yiled语句如果用在一个表达式中,必须放在圆括号中
> yield语句用作函数参数或用于赋值表达式的右边,可以不加括号
(2)Generator函数&遍历器对象
1)Generator函数的this
调用Generator函数总是返回一个遍历器对象,ES6规定这个遍历器对象是Generator函数的实例,即:
function* foo(){
....
}
let g=foo();
console.log(Object.getPrototypeOf(g)===foo.prototype); //true
注:> 因为遍历器对象是Generator函数调用后默认的返回值,因此Generator函数不能用作普通的构造函数,因为new操作符会必须设置构造函数的
默认返回值为this,因此new操作符会失效
> 在Generator函数内部使用this,判断this指向的方法和普通的函数一样
2)遍历器对象的API
调用Generator函数生成的遍历器对象,拥有next()、throw()、return()三个方法,均继承自原型对象,即Generator.prototype.next()、
Generator.prototype.throw()、Generator.prototype.return()
>> Generator.prototype.next()方法
yiled语句本身没有返回值,或者说总是返回undefined,而next()方法除了可以启动对Generator函数的执行,也可以带一个参数,该参数
会被当做上一条yield语句返回的值,因而可以在Generator函数运行的不同执行阶段,从外部向内部注入不同的值,从而调整函数的行为
注:第一次调用next()方法的时候不能带有参数
>> Generator.prototype.throw()方法
throw()方法用来在函数体外抛出错误,然后再Generator函数体内捕获
>> Generator.prototype.return()方法
return()方法可以返回制定的值,并终结Generator函数的遍历
注:若Generator函数内部有try...finally代码块,那么return方法会推迟到finally代码块执行完再执行
(3)遍历器对象与for...of循环
1) for...of循环
正如在Iterator章节中提到的,for...of循环就是专门用来自动循环调用遍历器对象的next()方法,并将返回对象的value属性值赋给变量,以供
外部使用,因此可以使用for...of循环来自动遍历Generator函数的内部状态
注:
> 一旦next()方法的返回对象的done属性为true,for...of循环就会终止,且不包含该返回的对象
> 除了for...of循环,扩展运算符、结构赋值这些内部调用for...of循环的操作,也可以用来自动遍历Generator函数内部的状态
> 因为遍历器对象的存在,Generator函数完全可以模拟出一个数据结构
2) yiled*语句和for...of循环
yield*语句是for...of循环的一种简写方法,等同于在Generator内部部署一个for...of循环,用来在Generator函数内部调用另一个Generator
函数,如下所示:
function* foo(iter1){
yield* iter1;
}
//等同于
function* foo(iter1){
for(value of iter1){
yield value;
}
}
注:> 任何数据结构,只要有iterator接口,就可以在Generator函数内部用yield*语句遍历
> 如果被代理的Generator函数有return语句,就可以向代理他的Generator函数返回数据
(3)Generator函数的应用
1)部署Iterator接口
2)模拟数据结构(Generator函数推导)
3)应用于异步编程
5、async函数
四、Module
1、ES6 Module的优点
• ES6 Module采用‘编译时加载’方式,在编译时就完成模块编译,无需加载整个模块,相比较于CommonJS和AMD规范(RequireJS)采用的‘运行时加载’方式,需要整体加载模块,
ES6 Module的效率会更高
• ES6 Module不再需要UMD模块格式,服务器和浏览器均能支持ES6 Module
• 将来浏览器的新API可以采用模块格式提供,不再需要做成全局变量或其他对象的属性
• 不再需要对象作为命名空间(如Math对象),可以通过模块提供
2、ES6 Module的命令
(1)模块定义和导出
#1 export命令
一个模块就是一个独立的文件,export命令规定了模块的对外接口,其语法如下:
>> 单个变量、函数或类的输出
//example.js
export let baz='javaScript';
export let bar='module';
export function foo(){}
export class Person{}
>> 多个变量或函数统一输出
//example.js
let baz='javaScript';
let bar='module';
function foo(){};
class Person{};
export{baz,bar,foo,Person};
>> as关键字重命名
//example.js
let baz='javaScript';
let bar='module';
function foo(){};
class Person{};
export{
baz as renameBaz,
bar as renameBar,
foo as renameFoo,
Person as renamePer
}
注:• export命令可以出现在模块的任何地方,只需要处于模块的顶层即可(若在块级作用域内,会报错)
• export语句输出的值是动态绑定的,绑定其所在的模块
#2 export default命令
使用export default命令,可以为模块指定默认输出,其他模块在加载该模块时,import命令可以为该默认输出指定任意的名称,语法如下:
//example.js
export default function(){
console.log('hello wrold');
}
//main.js
import someName form 'example';
someName(); //'hello wrold'
注:• export default后面可以接任何形式的表达式,但加载时都视为匿名的
• 一个模块只能有一个默认输出(不能使用大括号输出多个默认值),因此export default命令只能使用一次
• 相对应的import命令输入默认值时,不能使用大括号,因为export default只能输出一个默认值
• 可以在一条import语句中同时输入默认值和其他变量,如下:
import someName,{ baz,bar,foo,Person} from 'example'
• 本质上,export default命令就是输出一个叫做default的变量或方法,然后在相应的import命令中为其重命名
(2) 模块引入
#1 import命令
与export命令相对,import命令用于输入其他模块提供的功能,语法如下:
>> 统一输入所需的功能
//main.js
import {baz,bar,foo,Person} from 'example'
>> as关键字为输入的功能重命名
//main.js
import {baz as Baz} from 'example'
注:• 必须用大括号,且大括号中要输入的变量名必须和被导入模块对外接口的名称相同
• import命令具有提升效果,会提升到整个模块的头部首先执行
• 若在一个模块中先输入后输出同一个模块的的一个功能,可以将import和export命令写在一起,如下:
import {es6} from 'someModule';
export default es6;
可简写为:
export {es6} from 'someModule';
export {es6 as renameEs6} from 'someModule'; //将其重命名输出
ES7的改进:
export es6 from ''someModule;
• import命令可以用来执行所加载的模块,如下:
import 'someModule'
#2 模块整体加载命令
ES6 Moudle中,要想整体整体加载,可以在使用import命令是,用*指定一个对象,所有的输出值都加载在这个对象上,或者直接采用module命令,语法如下:
>> import命令+ *
//main.js
import * as totalObj from 'example';
console.log(totalObj.baz + ' ' + totalObj.bar); //'javaScript module'
>> module命令
//main.js
module totalObj from 'example';
console.log(totalObj.baz + ' ' + totalObj.bar); //'javaScript module'
#3 模块的继承命令
正如在import命令中提到的,若在一个模块中先输入后输出同一个模块的的一个功能,可以将import和export命令写在一起,此方法可以实现模块之间的继承,如下:
//main.js
export * from 'example';
注:export *会输出example.js模块的所有属性和方法,即完成继承,但不会输出example.js的默认输出,因为main.js可能也会有默认输出,会导致冲突
(3)模块标识
模块标识其实就是form关键字后面的字符串参数
3、ES6 Module加载的实质与循环加载
(1)加载实质
ES6 Module加载的机制与CommonJS规范模块完全不同,模块输出的只是值的引用,即模块遇到import命令时,不会去执行模块,只会生成一个
动态的只读引用,并且不会缓存值,模块里面的变量绑定其所在的模块
(2)循环加载
ES6 Module所有的依赖关系以及变量的输入和输出在编译时就已经确定,遇到import命令时不会去执行模块脚本,只是生成一个只读的引用,只要引用是存在的,
代码就能执行
4、ES6 Module的转码
ES6 Module在Node上的支持度较好,但浏览器还不支持,可以采用Babel、ES6 module transpiler、SystemJS来转码
马建仓 AI 助手
尝试更多
代码解读
代码找茬
代码优化
JavaScript
1
https://gitee.com/zweekend/javascript-lang-notes.git
git@gitee.com:zweekend/javascript-lang-notes.git
zweekend
javascript-lang-notes
javascript-lang-notes
master

搜索帮助