ES6 详解

283 阅读19分钟
前言

最近读了阮一峰老师的 《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 这一点

  1. Symbol对象
  2. Set和Map
  3. Iterator 和 for...of
  4. 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接口的数据结构如下:

  1. Array
  2. Map
  3. Set
  4. String
  5. TypedArray
  6. 函数的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 =