前言
最近读了阮一峰老师的 《ES6标准入门》,这本书涵盖的干货很大,短时间内吸收的有些力不从心。现在重新温习一遍,并把我个人认为比较 常用的方法 或者需要注意的点简单总结下。 另外我将主要章节的知识树作成了 思维导图 分享,部分内容可能在表达上不严谨,仅供初学者参考使用。
1.let 和 const
- let不会像var一样,存在变量提升 ,所以必须先声明才能使用。
{ x ++; let x = 1; //错误 } - 块级作用域内不允许重复声明变量
{ let x = 0; let x = 1; //错误 } - 块级作用域的let声明,可以不受外层影响。
- 块级作用域函数声明,相当于var声明变量
- const 声明常量 值不可改动
- const 声明变量 指向的内存地址不可改动
const a =[]; a.push(0); //正确 a = new Array() //错误
2.解构赋值
let [a,b,c] = [1,2,3] ; 相当于 let a,b,c; a = 1;, b = 2 ; c = 3;- 默认值
let [a = 1 , b = 2] = []; 相当于 let a = 1,b = 2 - 对象也可以解构赋值
let {a:a , b:b} = {a:0,b:1} 可简写为 let {a , b} = {a:0,b:1} - 函数参数的解构赋值可以这么写
function add( [x, y]) {
return x + y
}
add([1,2]) //3
function add( {x = 1, y = 2} = {}) { //无参情况下默认参数为一个空对象
return x + y
}
add(); //3
- 尽量不用圆括号
- 提利用解构赋值取json数据会很方便
具体翻阅ES6标准入门第三章, 这里就不举例了
3.字符串的拓展
- for...of 可以作用在字符串上,遍历单个字符
- 模板字符串
let x = 'world'
let s = `hello ${x}`
//相当于
let t = 'hello' + x
新增方法
- includes() 判断是否包含目标字符串
- startWith() 判断是否以目标字符串开头
- endWith() 判断是否以目标字符串结尾
- repeat() 将原字符串重复n次
4.正则表达式的拓展
- u修饰符 能够识别非规范的大小写
let reg = /xyz/iu - y修饰符 与g类似,具有"粘连"效果。 y会保证下一次匹配必须能在开头位置匹配到
let s = 'aa_aa'
let r1 = /a+/g
let r2 = /a+/y
r1.exec(s) //['aa']
r1.exec(s) //['aa']
r2.exec(s) //['aa']
r2.exec(s) //null //第二次匹配开头为 ' _ '
- s修饰符 带有s修饰符的.字符可以匹配包括 /n /r等任意单个字符
- 后行断言 /(?<=y)x/ ,x只有在y的后边才匹配 如匹配' 50%'
- 后行断言否定 /(?<!y)x/
let reg = /(?<=\$)\d+/ //后行断言
'$5'.match(reg)
//["5", index: 1, input: "$5", groups: undefined]
- 具名组匹配 使用(?< name >) 形式给组匹配起名
let reg = /(?<age>\d+)-(?<year>\d+)/
'21-2020'.replace(reg,'$<year> and $<age>') //2020 and 21
- source 和 flags
let reg = /(?<age>\d+)-(?<year>\d+)/iu
reg.flags //"iu"
reg.source //"(?<age>\d+)-(?<year>\d+)"
(二)
继上一节,本节继续巴巴 =v=
1.数值的拓展
这一张主要是Number和Math新增方法,上图!
2.函数的拓展
- 函数的默认值 这个第一节讲过,可以和解构赋值组合使用
function add( x = 3, y = 5) {
return x + y
}
add() //8
function add( [x = 3, y = 5] = []) { //无参情况下默认参数为一个空数组
return x + y
}
add([]) //8
function add( {x = 1, y = 2} = {}) { //无参情况下默认参数为一个空对象
return x + y
}
add(); //3
- 默认值尽量放在尾部,不然容易出现以下的错误
function add( x = 3, y ) {
return x + y
}
add(5) //NaN 因为y为 undefined 函数内会计算 5+undefined === NaN
-
函数默认值将不计入函数的length属性,以上面最近的add函数为例
add.length === 1而不是2 -
函数的name属性 返回函数名 其中构造函数返回的实例 返回 anonymous , bind返回的 会加上 bound 前缀
-
箭头函数
() => console.log('Hello')
//等同于
function(){
console.log('hello')
}
let x = (x,y) => {
return x + y
}
//等同于
let x = function(){
return x + y
}
箭头函数要注意以下特点 1.箭头函数体内的this是定义时所在的对象,不用担心this的指向问题 2.箭头函数可嵌套使用
let x = (x) => {
return (y) => {
return x+y
}
}
x(1)(2) //3
这个函数也算是柯里化的一种
上图!
3.数组的拓展
这个没啥好讲的。。(敷衍中。。)
我直接上图,可以参考,缕着看书吧,哈哈哈哈
4.对象的拓展
接下来到最重要的 ==对象==了。 没有对象,虽然是无忧无虑,但时间久了也是很让人头疼。
-
简洁表示
对象内属性名如果是属性值那个变量,可以省略属性值不写
let obj = { dx : dx }等同于let obj = { dx }对象中的方法也可以省略function
let obj = {
let [x,y] = [1,2]
/* function add() { 替换成下面的写法
return this.x+this.y
} */
add () {
return this.x+this.y
}
}
-
属性名表达式
let obj = {}
obj['a'+ 'b'] = '有对象'
log(obj)
> {ab: "有对象"}
个人觉得应该不是很常用
-
name属性
返回 ==对象方法== 的name 这个可以看上边的 函数的拓展
-
常用Object新增方法
1.Object.is() //比较两个值是否相等,代替===
2.Object.assign()
1.拷贝字符串
字符串以数组形式复制到目标对象,其他值不可以为首参数 2.拷贝对象 浅拷贝。如果源对象属性值为对象,只能复制它的引用。即obj1修改了对象属性的值,obj2随即改变。3.Obeject.setPrototypeOf(object,prototype)
Obeject.getPrototypeOf() 等同于设置和读取 __proto__4.Object.keys() Object.values() Object.entries() 返回Object的键名、键值,键值对
let x = {
a:'aaa',
b:'bbb',
c:'ccc'
}
Object.keys(x)
(3) ["a", "b", "c"]
Object.values(x)
(3) ["aaa", "bbb", "ccc"]
Object.entries(x)
(3) [Array(2), Array(2), Array(2)]
0: (2) ["a", "aaa"]
1: (2) ["b", "bbb"]
2: (2) ["c", "ccc"]
length: 3
__proto__: Array(0)
-
枚举性
1.Object.getOwnPropertyDescriptor(*obj,'a') 获取该对象自身属性的描述对象 包括
- value
- writable
- enumerablle
- configurable
2.Object.getOwnPropertyNames(obj) 获取该对象自身的所有属性 (除symbol属性)
3.Object.getOwnPropertyDescriptors(*obj,'a') 获取该对自身象的所有属性
-
含Symbol属性的遍历
Object.getOwnPropertySymbols(obj) --包含对象自身的所有Symbol类型的属性 Reflect.ownKeys(obj) --返回obj自身的 ==所有属性==,包括不可枚举的属性 ( ps:这个是真正的所有)
(三)
本节主要是以下内容,其中重点是 proxy和Reflect 这一点
- Symbol对象
- Set和Map
- Iterator 和 for...of
- proxy和Reflect
-
1.Symbol对象
在很多时候,我们在定义对象的属性时很容易遇到重名的情况,或者使用第三方的插件时,容易遇到重名的情况。ES6引入了第七种数据类型Symbol,作为一个具有 独一无二 的值存在。 所以现在定义一个对象,它的属性名可以是字符串,也可以是Symbol。
talk is cheap , show me the code!
const sym = Symbol()
const bar = Symbol('bar')
const mySym1 = Symbol('mySym')
const mySym2 = Symbol('mySym')
上面的四行代码均会声明一个独一无二的Symbol对象,Symbol()括号里传入的参数,是对这个对象的描述,当你在控制台打印这些对象时,不至于全都返回Symbol,当然你也可以不写。建议和变量名一致就好。下面是这四个Symbol的控制台打印情况
sym
--Symbol()
bar
--Symbol(bar)
mySym1
--Symbol(mySym)
mySym2
--Symbol(mySym)
mySym1 === mySym2
--false
在作为属性名的时候,Symbol对象的属性名要用方括号赋值,而不用点运算符。
let obj = {}
obj[mySym1] = 'hello' //正确
obj.mySym2 = 'hello' //错误 尽量不要这样!
另外Symbol作为属性名。不会被常规方法遍历到 如 for..in,Object.getOwnPropertyNames,Object.keys 会被 Object.getOwnPropertyDescriptors(obj) , Reflect.ownKeys(obj) , Object.getOwnPropertySymbols(obj) 遍历到 Object.getOwnPropertySymbols(obj) 会返回自身所有的Symbol属性
详细介绍请看上一章。 ES6学习总结(二)
Symbol.for() 和 Symbol.keyFor()
Symbol具有==登记机制==,Symbol.for() 和Symbol() 作用类似,会新建一个Symbol对象。 不同的是,比如,Symbol.for('mySym')会在新建之前检查这个mySym参数,
看看有没有同名的,如果有,就不会新建Symbol对象,而是返回这个值。
因此,
let a = Symbol('mySym') ; let b = Symbol('mySym') ; let c = Symbol('mySym')
会创建三个不同的Symbol
let a = Symbol.for('mySym') ; let a = Symbol.for('mySym') ;let a = Symbol.for('mySym')
会创建三个相同的值。a === b === c --true
,并且,Symbol.for() 新建的Symbol会被==登记==,Symbol.keyFor() 就是用来返回一个已登记对象的描述。
let a1 = Symbol.for('mySym')
let a2 = Symbol('mySym')
Symbol.keyFor(a1)
"mySym"
Symbol.keyFor(a2)
undefined
Symbol对象有11个内置的方法,在调用某些常规方法时,JavaScript内部会去引用这些内置方法。这里不予讨论。
-
2.Set和Map
Set Set是一个新的数据结构,他同数组一样,不过Set的成员是唯一的,不可重复。 Set本身作为一个构造函数,可以生成Set数据结构。
WeakSet 类似Set,不同的是,它的成员只能是对象,并且都是弱引用。
Map
Map的作用和对象类似,不同的是,对象的键只能为字符串或者Symbol对象。 但是Map的键为任何JavaScript类型。 可以理解成,键-值对 --> 值-值对。是更为完善的Hash结构。
WeakMap
类似Map,不同的是,它的成员只能是对象,并且都是弱引用。
WeakSet和WeakMap保存的对象一旦消除引用,内存就会被自动回收,所以WeakSet和WeakMap适合保存 ==DOM== 节点,当DOM节点不存在时,其引用也会被释放。
-
3. Iterator 和 for...of
引用下原文的介绍
JavaScript 原有的表示“集合”的数据结构,主要是数组(Array)和对象(Object),ES6 又添加了Map和Set。这样就有了四种数据集合,用户还可以组合使用它们,定义自己的数据结构,比如数组的成员是Map,Map的成员是对象。这样就需要一种统一的接口机制,来处理所有不同的数据结构。 迭代器(Iterator)就是这样一种机制。它是一种接口,为各种不同的数据结构提供统一的访问机制。任何数据结构只要部署 Iterator接口,就可以完成遍历操作(即依次处理该数据结构的所有成员)。 Iterator 的作用有三个:一是为各种数据结构,提供一个统一的、简便的访问接口;二是使得数据结构的成员能够按某种次序排列;三是 ES6 创造了一种新的遍历命令for...of循环,Iterator接口主要供for...of消费。
原生具备Iterator接口的数据结构如下:
- Array
- Map
- Set
- String
- TypedArray
- 函数的argument对象
它们可以使用for..of 遍历 或向下面那样调用 以Array为例 可以这样调用
const arr=[1,2,3];
const it=arr[Symbol.iterator]();
it.next()
{value: 1, done: false}
it.next()
{value: 2, done: false}
it.next()
{value: 3, done: false}
it.next()
{value: undefined, done: true}
我对Iterator的理解还在学习阶段,之后我会更新这一块的内容。
-
4.Proxy与Reflect
(1).Proxy
Proxy属于元编程,修改操作的默认行为,在语言层面做出修改。 在之前,我们会用 Object.defineProperty 来对数据进行拦截。
let obj = {
b:'bbb',
c:'ccc'
}
let arr = [1,2,3]
Object.defineProperty(obj,'a',{
enumerable:true,
configurable:true,
get(){ // 读,当我们读取时,则会执行到get,比如obj.a
return 'you can not getting a!'
},
set(newValue){ // 写,当我们写入时,则会执行到set,比如obj.a = 'hhhh'
this.b = 'wow'
}
})
下面是控制台打印情况
obj.a
"you can not getting a!"
obj.a = '5'
"5"
obj.b
"wow"
和Object.defineProperty不同的是,==Proxy== 是在数据外层设置一个拦截层,访问内部的数据时,要先经过这层拦截。 就像proxy的词义一样,是。。。
查一下。
有代理的意思。
let proxy = new Proxy(target,handler) 来看这段代码。 target 和 handle分别为所要拦截的对象和定制拦截行为的对象。 两个参数都是对象。 那么可以这样写
var pro =new Proxy({},{
get : () => 'you are getting me'
})
pro.name
"you are getting me"
Proxy提供了一些实例方法,可以根据所需进行拦截。如 set() 拦截属性的赋值操作 apply() 拦截函数的调用、call、apply操作 has() 拦截HasProperty操作。 construct() 拦截new命令 。 等等,这里就不一一枚举了,下边的图里会有所有的这些方法以及作用。
(2).Reflect
Reflect将Object对象的一些明显属于语言内部的方法放到Reflect上,让Object操作由命令式都变成函数行为。它修改某些Object方法的返回结果,让其变得更合理。
Reflect与Proxy对象方法一一对应。
let obj = { name: 'li lei'}
obj.name //li lei
新写法
Reflect.get(obj, 'name') //li lei
其他方法参考下图
(四)
本章准备介绍下==Promise==对象,==Generator==函数和==async==函数。
-
1.Promise对象
在之前,我们进行异步的操作时,通常会使用回调函数。 比如要读取三个文件,如果只能是在读取上一个,才能打开下一个文件的情况下,依次读取他们,并将他们头尾拼合在一起。
fs.readFile(fileA, function (err, data) {
let result = data.toString();
fs.readFile(fileB, function (err, data) {
result += data.toString();
fs.readFile(fileB, function (err, data) {
result += data.toString();
// ...
});
});
});
这样让函数层层嵌套,最后返回result。 但是如果有一种情况,需要读取几十个甚至成千上万的文件,岂不要是写几千个回调函数? 看到这,不禁让人一阵冷颤,这就是俗称的'回调地狱'。 ES6提出了Promise对象,用来解决这种异步执行的问题。Promise对象可以看作一个容器,保存着某个未来才会结束的事件。大部分情况下都是异步操作。
可以这样来声明一个Promise对象
let myPro = new Promise()
let myPro = new Promise(function(resove,reject){resove() })
let myPro = new Promise( (resove,reject) => {reject('err') })
Promise对象的状态不受外界影响,它本身存在三种状态Pending、Fulfilled、Rejected。可以分别理解为,正在进行、成功、失败。 它只有两种情况 ==Pending ----> Fulfilled== 进行到成功 ==Pending ----> Rejected== 进行到失败 一旦成功或失败,Promise就会定型,再也不会改变状态。
Promise也有以下缺点 1.无法中途取消,一旦新建立即执行 所以可以写成这种形式
function promise () {
return new Promise ( function (resolve, reject) {
if ( success ) {
resolve(a)
} else {
reject(err)
}
} )
}
2.未设置回调函数,内部抛出错误不会反应到外部 3.处于Pending时,无法得知进展情况
实例方法 1.Promise.prototype.then() then() 接受两个==函数==作为==参数==。
let pro = new Promise((resolve, reject) => {
if ( success ) {
resolve(val)
} else {
reject(err)
}
})
pro.then(
(resolve) => {
log('成功')
},
(reject) => {
log('失败')
}
)
then()返回新的Promise实例,所以可以进行链式调用
let pro = new Promise((resolve) => {
resolve(1)
})
pro.then((resolve) => {
return ++resolve
}).then((resolve)=>{
resolve++
log(resolve)
})
2.Promise.prototype.catch()
相当于Promise.prototype.then(null,reject(){ })
用来捕获错误,可以这样用
let pro = new Promise((resolve,reject) => {
throw new Error('这有个错误')
})
pro.catch((err) => {
log(err)
//Error: 我也是个错误
//at new Promise (<anonymous>)
})
//亦或
let pro = new Promise((resolve,reject) => {
reject('我也是个错误')
})
pro.catch((err) => {
log(err) //我也是个错误
})
还有两个能够包装多个Promise的方法。
3.Promise.all() 可以把多个Promise对象用数组包装起来,返回一个整体的Promise。 其中必须所有的小Promise都得成功,大的才成功。 直译过来就是 《一个也不能少》
var promise = Promise.all( [p1, p2, p3] )
4.Promise.race() 用法同上,其中只要一个小Promise成功,大的就成功。 直译过来就是 《一起来赛跑 》
var promise = Promise.race( [p1, p2, p3] )
5.Promise.resolve() 将现有对象转为Promise对象,有两种情况
(1)参数是thenable对象 将对象转为Promise对象,然后立即执行thenable对象的then方法 (2)不具有then方法或根本不是对象 返回一个新的Resolved状态的Promise对象
6.Promise.reject() 返回一个Rejected状态的Promise对象
有关Promise得就到这里
-
2.Generator函数
用原书上话说,Generator函数是一个状态机,也是一个遍历器对象生成函数。 怎么理解都可以。 用法如下,在function和 函数名中间加一个 * 号。 函数中yield相当于一个暂停按钮。声明一个变量储存Generator函数。然后用这个变量调用next()方法。 每次调用,都会在yield暂停,同时执行并返回yield后边的内容
function * generator(){
x = 0
yield ++x
yield ++x
yield ++x
return 'hhh'
}
let my_generator = generator()
my_generator.next()
//{value: 1, done: false}
my_generator.next()
//{value: 2, done: false}
my_generator.next()
//{value: 3, done: false}
my_generator.next()
//{value: "hhh", done: true}
next()方法可以有参数,作为上一个yield返回的值。来证明下
function * generator(){
x = 0
let y = yield x++
yield y
return 'hhh'
}
let my_generator = generator()
my_generator.next()
//{value: 0, done: false}
my_generator.next(10)
//{value: 10, done: false}
这里的10赋值给了 yield x++ 语句 , yield x++ 语句把值又传给了变量y ‘
yield* yield可以返回一个对象(字符串,数值,函数)作为返回值,如果想在Generator函数内调用另一个Generator函数,需要用到yield*语句
function * generator(){
x = 0
yield ++x
yield * aad()
yield ++x
return 'hhh'
}
function* aad(){
yield 'a'
}
let my_generator = generator()
my_generator.next()
//{value: 1, done: false}
my_generator.next()
//{value: "a", done: false}
my_generator.next()
//{value: 2, done: false}
my_generator.next()
//{value: "hhh", done: true}
比如利用yield*中序遍历一个二叉树,可以一步一步执行
function * inorder (root) {
if( root ){
yield * inorder(root.left)
yield root.val
yield * inorder(root.right)
}
}
另外Generator函数可作为对象属性,在方法前加 * 即可
使用 for...of 可以使Generator函数自动执行
function * generator(){
this.x = 0
yield x++
yield x++
return 'hhh'
}
for(let i of my_generator ){log(i)}
// 0
//1
Generator函数的this会失效,也不能作为构造函数使用。有种解决办法, 1.声明一个空对象obj,将obj的__proto__指向Generator函数的prototype。 2.直接绑定到generator的内部this
let obj = {}
Reflect.setPrototypeOf(obj,generator.prototype)
//或者
let my_generator = generator.call(generator.prototype)
还有一些其他方法,这块不细说了。感兴趣可以自己去敲一敲。
Generator还有两个原型方法
1.Generator.prototype.throw() 可以在函数体外抛出错误,在Generator函数体内进行捕获
2.Generator.prototype.return() 可以返回给定的值,并终结Generator函数的遍历
-
3.async函数
async函数是Generator函数的语法糖,async函数可以看作多个异步操作,包装成的一个Promise对象,await命令就是内部then命令的语法糖。
async函数的写法 在function前加async async function asyncPrint(value, ms)
在函数内使用await 命令,函数会随着await命令之后返回的Promise对象一步一步自动执行。
先看以下代码
let result = 0
function timeout(ms) {
result++
return new Promise((resolve) => {
setTimeout(resolve, ms);
});
}
async function asyncPrint(value, ms) {
await timeout(ms); console.log(result)
await timeout(ms); console.log(result)
await timeout(ms); console.log(result)
await timeout(ms); console.log(value)
}
asyncPrint('hello world', 1000).catch(e => log(e))
上边的代码,定义了一个全局遍历result,并且声明了timeout函数,该函数会让result+1,并且返回一个resolve状态的Promise,从而引起下一个await执行。 所以你可能也猜到了,这段代码执行后,会在1000毫秒后打印1,再过1000毫秒。打印2。。。。最后打印hello world
1
2
3
hello world
==注意== await命令后面可能不是一个Promise对象,如果是这种情况,会被转成一个立即 resolve 的Promise对象。
说说相对于Generator函数的优点
- async内置执行器
- 具有更好的语义
- 更广的适用性
- 返回值是Promise,而不是Interator对象
async函数返回一个Promise对象。可以用then添加回调函数。
asyncPrint('hello world', 1000).catch(e => log(e)).then(()=>{})
async函数会在最后执行这个回调函数。
async函数内部抛出错误,会导致返回的Promise对象变为reject状态,一旦出现这样的错误,那么整个async函数都会中断执行。所以某些情况下,把await放在try...catch结构里面,还是很有必要的。
最后提及一点,有时候异步执行的命令可能不需要’异步‘,也就是不一定的相继发生的,可以同步进行。这时候在async函数中,可以这样写
//把上边的代码改成await Promise.all([getFoo(), getBar()]);
async function asyncPrint(value, ms) {
await timeout(ms);
console.log(result)
await Promise.all([timeout(ms),timeout(ms),timeout(ms)])
console.log(result)
await timeout(ms);
console.log(value)
}
asyncPrint('hello world', 1000).catch(e => log(e))
还是每隔1000毫秒打印一段数据,不过这次是1,4,hello world 三个timeout(ms),同时执行了!
到现在就介绍的差不多了,相信你也有了一定的理解。
(五)
class和class继承
为什么要引入class? 要知道,JavaScript是没有类的概念的,我们在面向对象编程的时候,通常会是编写一个构造函数。把属性写在函数里,方法放在原型上。
function constructor(a,b){
this.a = a;
this.b = b;
}
constructor.prototype.add = function (){
return this.a + this.b
}
var aa = new constructor(1,2)
aa.add() //3
这样的写法很容易让Java、python等编程人员产生疑惑,引入class,作为一个语法糖来编写,在表现形式上更靠近传统的面向对象语法。不过究其根本,本质上还是原型。下面的新写法和python的对比。
class People{ //或者 let People = MyPeople {
constructor(n,a){
this.name = n
this.age = a
}
speack() {
console.log(`${this.name}说: 我${this.age}岁了`)
}
}
let s= new People('xiaoming',10); //let s= new MyPeople('xiaoming',10);
s.speack() //xiaoming说: 我10岁了
class People:
# 定义基本属性
name = ''
age = 0
# 定义构造方法
def __init__(self, n, a):
self.name = n
self.age = a
def speack(self):
print("%s 说: 我 %d 岁了" % (self.name, self.age))
s = People('xiaoming','10')
s.speack() #xiaoming说: 我10岁了
这样对比看起来是不是就很直观了。
以下要注意几点:
1.类的所有方法定义在类的prototype属性上
2.类的内部定义方法不可枚举 Object.keys(People) // []
3.类默认使用严格模式
4.类不存在变量提升,所以一定要先声明再使用。
==类的实例对象== 类使用new命令生成实例对象,而不能直接像python那样调用,否则会报错。 实例的属性除非显示定义在其本身(this对象上),否则都定义在原型class上,这样其实更符合实际情况。
==类的静态方法==
class People{ //或者 let People = MyPeople {
constructor(n){
this.name = n
}
speack(){ //类的普通方法
//...
}
static talk(){ //类的静态方法
//...
}
}
在方法名前加static 就变成了静态方法,看下图,静态方法在类prototype.constructor上,普通方法在prototype上。
所以,实例是不能继承静态方法的。 不过子类可以继承父类的静态方法。
==类的静态属性==
阮一峰老师在写《ES6标准入门》的时候,类的静态方法还只是作为一个提案,不过现在已经标准化了。 同静态方法一样,静态属性需要在属性名前加 static.
static talk ="talk"
==new.target== ES6为构造函数和类引入new.target属性。当通过new来生成实例时,内部的new.target会返回构造函数或类。 首先是构造函数,注意看下图的注释。
function People(){
log(new.target)
}
let myfoo1 = new People(); //返回构造函数
let myfoo2 = People(); //返回undefined
然后是类,当未使用new的时候就会报错
class People{
constructor(){
log(new.target)
}
}
let myfoo = new People(); //返回类
let myfoo = People(); //Uncaught TypeError: Class constructor People cannot be invoked without 'new'
==注意== 类有name属性,也有getter和setter。类的方法内部如果含有this,它将指向默认类的实例。 类的内部方法也支持Generator方法,
-
2.class继承
ES5在继承的时候,通常会让父函数使用apply或者call。绑定子函数的this去调用父函数的方法。 ES6的类继承机制不同。子类没有this,需要继承父类的this,进而对其加工。所以下面在子类中需要先super()继承父类this,然后才能使用this。 再来看看和python的对比
class People{
constructor(n,a){
this.name = n
this.age = a
}
}
class Kid extends People{
constructor(n,a,g){
super(n,a)
this.grade = g
}
speack() {
console.log(`${this.name}说: 我${this.age}岁了,${this.grade}年级`)
}
}
let s= new Kid('xiaoming',10,2);
s.speak()//xiaoming说: 我10岁了,2年级
class People:
# 定义基本属性
name = ''
age = 0
def __init__(self, n, a):
self.name = n
self.age = a
class Kid(People):
grade= ''
def __init__(self, n, a, g):
# 调用父类的构函
self.grade= g
s = Kid('xiaoming',10,2)
s.speak() #xiaoming说: 我10岁了,2年级
ES6类的写法相比之下和python2更像而不是上图的python3。
==super== 可作为 函数,也可作为 对象 使用。 ES6规定子类必须使用一次super()方法,来调用父类的构造函数。constructor(n,a,g){ super(n,a) this.grade = g }
注意下面是难点
作为 对象,super既能指向父类的prototype(原型方法),又能指向父类(静态方法,(注意是 方法,而不是属性) 。但不能指向父类的实例(实例方法)。。 super在普通方法中指向父类的prototype,在静态方法中指向父类。 直观来说就是。。。
let log = console.log.bind(console)
class People{
fu_lei_prototype() {
return 'fu_lei_prototype'; //可以取到父类的prototype
}
static fu_lei () {
return 'fu_lei' //可以取到父类
}
constructor(){
this.fu_lei_shili = 'fu_lei_shili'
}
}
class Kid extends People{
constructor(){
super() //作为函数使用,必须使用一次super()方法
log(super.fu_lei_shili) //undefined
log(super.fu_lei_prototype()) //fu_lei_prototype
}
static fu_lei () {
log(super.fu_lei())
}
}
log( Kid.fu_lei ) //fu_lei
这里有点稍微难理解,需要理清思路,可以先收藏回头再看。 另外提一嘴,super在普通方法中调用父类原型,会绑定到子类的this上。
class People{
people= '大汽车'
constructor(n,a){
this.name = n
this.age = a
}
speack() {
console.log(`${this.name}说: 我${this.age}岁了,我爱玩${this.people}`)
}
}
class Kid extends People{
people = '小玩具车'
constructor(n,a){
super(n,a)
}
}
let myfoo = new Kid('xiaoming',10);
myfoo.speack() // xiaoming说: 我10岁了,我爱玩小玩具车
so,这里绑定了子类的this,调用了Kid的people。所以myfoo.speack()输出是爱玩小玩具车而不是大汽车。
==类的__ proto__ 和 prototype==
class People{
}
class Kid extends People{
}
let man = new People()
let child = new Kid()
- 子类的__proto__总是指向父类
Kid.__proto__ === People //true
- 子类的prototype的__proto__总是指向父类的prototype
Kid.prototype.__proto__ === People.prototype //true
- 实例的__proto__的__proto__指向父类实例的__proto__
child.__proto__.__proto__ === man.__proto__ //true
还有最后,Module的加载实现
(六)
本节准备讲Module的加载实现。
- Module加载了
- Module实现了
本期内容就到此为止了,再见!。。。。 开个玩笑,最近写的太多了,有些疲乏,稍后我会更新本节内容。窝窝头,一块钱四个!
写的不错,点个赞鼓励下呗 =v =