2019-01-28
JavaScript
0

目录

let
const
数组的解构
字符串的扩展
数值的扩展
函数的扩展
箭头函数
promise
generator
iterator
Class
私有方法
私有属性
静态属性
静态方法
于ES5的不同
类不存在变量提升
类和模块的内部,默认就是严格模式
类定义的方法不可以被枚举
类必须使用new调用
WeakSet 和 WeakMap

当谈到JavaScript时,ES6(ECMAScript 6)是最重要的一个版本。它是JavaScript语言的一项重大更新,也被称为ECMAScript 2015。ES6引入了一系列的新功能,包括箭头函数、let和const关键字、类和模块等。学习ES6是每个现代JavaScript开发人员必备的技能之一。

let

块级作用域

  • 在 { } 内用let声明的变量,只在当前的{ } 内生效
  • 另外,for循环有一个特别之处,就是设置循环变量的那部分是一个父作用域,而循环体内部是一个单独的子作用域。所以for循环{ }内部访问的是for循环声明变量的父作用域

不存在变量声明提升

暂时性死区

变量x使用let命令声明,所以在声明之前,都属于x的“死区”

不允许重复声明

函数在块级作用域下

  • 允许在块级作用域内声明函数。
  • 函数声明类似于var,即会提升到全局作用域或函数作用域的头部。
  • 同时,函数声明还会提升到所在的块级作用域的头部
  • 块级作用域下的函数会被提升

const

  • 对于const来说,只声明不赋值,就会报错。
  • 其他特性与let一样

var命令和function命令声明的全局变量,依旧是顶层对象的属性;另一方面规定,let命令、const命令、class命令声明的全局变量,不属于顶层对象的属性。也就是说,从 ES6 开始,全局变量将逐步与顶层对象的属性脱钩。

数组的解构

如果解构不成功,变量的值就等于undefined。 image.png

image.png

默认值

解构赋值允许指定默认值。

image.png

对象的解构 first和last属于模式,模式不会被赋值

image.png

字符串的解构 image.png

字符串的扩展

image.png

数值的扩展

image.png

函数的扩展

参数默认值的位置

通常情况下,定义了默认值的参数,应该是函数的尾参数。因为这样比较容易看出来,到底省略了哪些参数。如果非尾部的参数设置默认值,实际上这个参数是没法省略的。

函数的length属性 指定了默认值以后,函数的length属性,将返回没有指定默认值的参数个数。也就是说,指定了默认值后,length属性将失真。

image.png

如果设置了默认值的参数不是尾参数,那么length属性也不再计入后面的参数了。 image.png

箭头函数

  • 内部this对象指向创建期上下文对象
  • 不能作为构造函数,也就是不能被new使用
  • 没有arguments
  • 不能作为生成器函数
  • 箭头函数的this会在函数申明的时候,就被绑定。后续你使用apply或者call都无法改变this作用域。
  • 普通函数的this作用域只有在被执行的时候,才会被绑定。

promise

  • then和catch都会默认返回成功状态的Promise
js
let 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

js
let 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

generator

js
function* 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

js
function* 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

iterator

对象迭代器实现

js
let 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())

Class

私有方法

私有方法和私有属性,是只能在类的内部访问的方法和属性,外部不能访问。这是常见需求,有利于代码的封装,但 ES6 不提供,只能通过变通方法模拟实现。一种做法是在命名上加以区别。

js
class Widget { // 公有方法 foo (baz) { this._bar(baz); } // 私有方法 _bar(baz) { return this.snaf = baz; } // ... }

上面代码中,_bar方法前面的下划线,表示这是一个只限于内部使用的私有方法。但是,这种命名是不保险的,在类的外部,还是可以调用到这个方法。 另一种方法就是索性将私有方法移出模块,因为模块内部的所有方法都是对外可见的。

js
class Widget { foo (baz) { bar.call(this, baz); } // ... } function bar(baz) { return this.snaf = baz; }

上面代码中,foo是公开方法,内部调用了bar.call(this, baz)。这使得bar实际上成为了当前模块的私有方法。 还有一种方法是利用Symbol值的唯一性,将私有方法的名字命名为一个Symbol值。

js
const 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()依然可以拿到它们。

js
const 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加了私有属性。方法是在属性名之前,使用#表示。

js
class IncreasingCounter { #count = 0; get value() { console.log('Getting the current value!'); return this.#count; } increment() { this.#count++; } }

上面代码中,#count就是私有属性,只能在类的内部使用(this.#count)。如果在类的外部使用,就会报错。

js
const counter = new IncreasingCounter(); counter.#count // 报错 counter.#count = 42 // 报错

上面代码在类的外部,读取私有属性,就会报错。

静态属性

js
// 老写法 class Foo { // ... } Foo.prop = 1; // 新写法 class Foo { static prop = 1; }

静态方法

注意,如果静态方法包含this关键字,这个this指的是类,而不是实例。 另外,从这个例子还可以看出,静态方法可以与非静态方法重名。

js
class Foo { static bar() { this.baz(); } static baz() { console.log('hello'); } baz() { console.log('world'); } } Foo.bar() // hello

于ES5的不同

类不存在变量提升

类不存在变量提升(hoist),这一点与 ES5 完全不同。

js
new Foo(); // ReferenceError class Foo {}

类和模块的内部,默认就是严格模式

类和模块的内部,默认就是严格模式,所以不需要使用use strict指定运行模式。只要你的代码写在类或模块之中,就只有严格模式可用。考虑到未来所有的代码,其实都是运行在模块之中,所以 ES6 实际上把整个语言升级到了严格模式。

类定义的方法不可以被枚举

类的内部所有定义的方法,都是不可枚举的(non-enumerable)

js
class 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"]

类必须使用new调用

js
class Foo { constructor() { return Object.create(null); } } Foo() // TypeError: Class constructor Foo cannot be invoked without 'new'

WeakSet 和 WeakMap

首先我们来理解下关于对象被垃圾回收的基本概念:

一个对象的内存地址如果被变量使用,则不会被回收,

当我们要释放内存地址时,可以将这个变量赋值为null或者其他值。

当这个内存地址没有被任何变量使用时,就会被回收,

函数运行完,会进行垃圾回收,除非存在闭包。

WeakSet和WeakMap

内存地址的引用类型为弱引用,即垃圾回收机制不将该引用考虑在内。 因此,只要所引用的对象的其他引用都被清除,垃圾回收机制就会释放该对象所占用的内存

执行:node --expose-gc ./node.js

js
global.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 许可协议。转载请注明出处!