当谈到JavaScript时,ES6(ECMAScript 6)是最重要的一个版本。它是JavaScript语言的一项重大更新,也被称为ECMAScript 2015。ES6引入了一系列的新功能,包括箭头函数、let和const关键字、类和模块等。学习ES6是每个现代JavaScript开发人员必备的技能之一。
块级作用域
不存在变量声明提升
暂时性死区
变量x使用let命令声明,所以在声明之前,都属于x的“死区”
不允许重复声明
函数在块级作用域下
var命令和function命令声明的全局变量,依旧是顶层对象的属性;另一方面规定,let命令、const命令、class命令声明的全局变量,不属于顶层对象的属性。也就是说,从 ES6 开始,全局变量将逐步与顶层对象的属性脱钩。
如果解构不成功,变量的值就等于undefined。
默认值
解构赋值允许指定默认值。
对象的解构 first和last属于模式,模式不会被赋值
字符串的解构
参数默认值的位置
通常情况下,定义了默认值的参数,应该是函数的尾参数。因为这样比较容易看出来,到底省略了哪些参数。如果非尾部的参数设置默认值,实际上这个参数是没法省略的。
函数的length属性 指定了默认值以后,函数的length属性,将返回没有指定默认值的参数个数。也就是说,指定了默认值后,length属性将失真。
如果设置了默认值的参数不是尾参数,那么length属性也不再计入后面的参数了。
jslet p = new Promise((resolve, reject) => {
reject(10)
})
p.then(() => {
console.log('success1')
}).catch(() => {
console.log('fail1')
}).then(() => {
console.log('success2')
}).catch(()=>{
console.log('fail2')
})
// fail1 第一步返回reject,所以执行fail1
// success2 第二步catch默认返回resolve,所以执行success2
链式下一个的then或者catch,会根据上一个是否成功,进行返回。而不是第一个成功了,后面所有的就都会走then
catch的执行,如果catch前面有三个then,任一一个then执行了reject,都会直接跳到catch
jslet p = new Promise((resolve, reject) => {
resolve(10)
})
p.then(() => {
console.log('success1')
return Promise.reject()
}).then(() => {
console.log('success2')
}).then(() => {
console.log('success3')
}).catch(()=>{
console.log('fail1')
})
// success1 第一步resolve,执行success1
// fail1 第二步执行reject,直接跳转到catch,执行fail1
jsfunction* test(){
let val= yield 3 // val值由下次的next实参决定,也就是300
console.log(val)
}
let iterator=test()
iterator.next() // 返回{value: 3, done: false}
iterator.next(300) // 这里传入的300,决定val的返回值,返回{value: undefined, done: true}
封装自执行generator
jsfunction* test () {
console.log('start')
let val1 = yield new Promise((resolve) => {
setTimeout(() => {
resolve(100)
}, 1000)
})
console.log('2',val1)
let val2 = yield '3'
console.log(val2)
console.log('end')
}
// 封装方法:
function co (fn) {
let iterator = fn()
function next (d) {
let result = iterator.next(d)
if (result.done) return
if (Object.prototype.toString.call(result.value) !== '[object Promise]') {
next(result.value)
return
}
result.value.then((data) => {
next(data)
})
}
next()
}
co(test)
//start
//2 100
//3
//end
对象迭代器实现
jslet obj={
a:1,
b:2
}
obj[Symbol.iterator]=function(){
let values=Object.values(obj)
let i=0
return {
next:function(){
return {
value:values[i++],
done:values.length>=i
}
}
}
}
let iterator=obj[Symbol.iterator]()
console.log(iterator.next())
console.log(iterator.next())
console.log(iterator.next())
私有方法和私有属性,是只能在类的内部访问的方法和属性,外部不能访问。这是常见需求,有利于代码的封装,但 ES6 不提供,只能通过变通方法模拟实现。一种做法是在命名上加以区别。
jsclass Widget {
// 公有方法
foo (baz) {
this._bar(baz);
}
// 私有方法
_bar(baz) {
return this.snaf = baz;
}
// ...
}
上面代码中,_bar方法前面的下划线,表示这是一个只限于内部使用的私有方法。但是,这种命名是不保险的,在类的外部,还是可以调用到这个方法。 另一种方法就是索性将私有方法移出模块,因为模块内部的所有方法都是对外可见的。
jsclass Widget {
foo (baz) {
bar.call(this, baz);
}
// ...
}
function bar(baz) {
return this.snaf = baz;
}
上面代码中,foo是公开方法,内部调用了bar.call(this, baz)。这使得bar实际上成为了当前模块的私有方法。 还有一种方法是利用Symbol值的唯一性,将私有方法的名字命名为一个Symbol值。
jsconst bar = Symbol('bar');
const snaf = Symbol('snaf');
export default class myClass{
// 公有方法
foo(baz) {
this[bar](baz);
}
// 私有方法
[bar](baz) {
return this[snaf] = baz;
}
// ...
};
上面代码中,bar和snaf都是Symbol值,一般情况下无法获取到它们,因此达到了私有方法和私有属性的效果。但是也不是绝对不行,Reflect.ownKeys()依然可以拿到它们。
jsconst inst = new myClass();
Reflect.ownKeys(myClass.prototype)
// [ 'constructor', 'foo', Symbol(bar) ]
上面代码中,Symbol 值的属性名依然可以从类的外部拿到。
ES6提供的方法
class Foo {
#a;
#b;
constructor(a, b) {
this.#a = a;
this.#b = b;
}
#sum() {
return #a + #b;
}
printSum() {
console.log(this.#sum());
}
}
目前,有一个提案,为class加了私有属性。方法是在属性名之前,使用#表示。
jsclass IncreasingCounter {
#count = 0;
get value() {
console.log('Getting the current value!');
return this.#count;
}
increment() {
this.#count++;
}
}
上面代码中,#count就是私有属性,只能在类的内部使用(this.#count)。如果在类的外部使用,就会报错。
jsconst counter = new IncreasingCounter();
counter.#count // 报错
counter.#count = 42 // 报错
上面代码在类的外部,读取私有属性,就会报错。
js// 老写法
class Foo {
// ...
}
Foo.prop = 1;
// 新写法
class Foo {
static prop = 1;
}
注意,如果静态方法包含this关键字,这个this指的是类,而不是实例。 另外,从这个例子还可以看出,静态方法可以与非静态方法重名。
jsclass Foo {
static bar() {
this.baz();
}
static baz() {
console.log('hello');
}
baz() {
console.log('world');
}
}
Foo.bar() // hello
类不存在变量提升(hoist),这一点与 ES5 完全不同。
jsnew Foo(); // ReferenceError
class Foo {}
类和模块的内部,默认就是严格模式,所以不需要使用use strict指定运行模式。只要你的代码写在类或模块之中,就只有严格模式可用。考虑到未来所有的代码,其实都是运行在模块之中,所以 ES6 实际上把整个语言升级到了严格模式。
类的内部所有定义的方法,都是不可枚举的(non-enumerable)
jsclass Point {
constructor(x, y) {
// ...
}
toString() {
// ...
}
}
Object.keys(Point.prototype)
// []
Object.getOwnPropertyNames(Point.prototype)
// ["constructor","toString"]
采用 ES5 的写法,toString方法就是可枚举的。
var Point = function (x, y) {
// ...
};
Point.prototype.toString = function() {
// ...
};
Object.keys(Point.prototype)
// ["toString"]
Object.getOwnPropertyNames(Point.prototype)
// ["constructor","toString"]
jsclass Foo {
constructor() {
return Object.create(null);
}
}
Foo() // TypeError: Class constructor Foo cannot be invoked without 'new'
首先我们来理解下关于对象被垃圾回收的基本概念:
一个对象的内存地址如果被变量使用,则不会被回收,
当我们要释放内存地址时,可以将这个变量赋值为null或者其他值。
当这个内存地址没有被任何变量使用时,就会被回收,
函数运行完,会进行垃圾回收,除非存在闭包。
WeakSet和WeakMap
内存地址的引用类型为弱引用,即垃圾回收机制不将该引用考虑在内。 因此,只要所引用的对象的其他引用都被清除,垃圾回收机制就会释放该对象所占用的内存
执行:node --expose-gc ./node.js
jsglobal.gc(); // 查看内存占用的初始状态,heapUsed 为 4M 左右
console.log('未使用',process.memoryUsage());
let wm = new WeakMap();
// 新建一个变量 key,指向一个 5*1024*1024 的数组
let key = new Array(5 * 1024 * 1024);
wm.set(key, 1);
global.gc();
// 这时内存占用 heapUsed 增加到 45M 了
console.log('已使用',process.memoryUsage());
// 清除变量 key 对数组的引用,
// 但没有手动清除 WeakMap 实例的键名对数组的引用
key = null;
global.gc();
// 内存占用 heapUsed 变回 4M 左右,
console.log('清除后',process.memoryUsage());
执行结果: 未使用 4472832 已使用 6832128 清除后 6832128
本文作者:BARM
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!