ES6 系列 (上)

Symbol

Symbol 是 ES6 中新增的一种原始类型。

var s1 = Symbol('s')
var s2 = Symbol('s')

console.log(s1 == s2) // false
console.log(typeof s1) // 'symbol'

Symbol 类型通过调用 Symbol() 函数生成,每一个 Symbol 类型的值都是唯一的,即使在调用 Symbol() 时传入的参数相同,返回的值并不同。

Symbol 类型能转换成布尔值(true)或字符串,但不能转化成数字

Symbol 类型作为对象的 key 可以私有化属性

普通闭包方式创建有私有属性的构造函数:

var Person = (function() {
// 私有属性
var _gender
function P(name, gender) {
this.name = name
_gender = gender
}
P.prototype.getGender = function() {
return _gender
}
P.prototype.setGender = function(n) {
_gender = n
}
return P
})()

var p = new Person('张三', '男')
console.log(p) // P {name: "张三"}
console.log(p.getGender()) // 男
p.setGender('女')
console.log(p.getGender()) // 女

Symbol + 闭包实现私有化属性:

var Person = (function() {
var _gender = Symbol('gender')
function P(name, gender) {
this.name = name
this[_gender] = gender
}
P.prototype.getGender = function() {
return this[_gender]
}
P.prototype.setGender = function(n) {
this[_gender] = n
}
return P
})()

var p = new Person('张三', '男')
console.log(p[Symbol('gender')]) // undefined
console.log(p.getGender()) // 男
p.setGender('女')
console.log(p.getGender()) // 女

使用 Symbol + 闭包的方式私有化属性会安全。仅使用闭包会出问题,_gender 属性只有一个,所有 Person 实例会共用同一个。

let、const

var 的特点

  • 支持变量声明预解析
  • 不支持块级作用域
  • 可以重复声明
console.log(a)  // undefined
{
var a = 1
}
console.log(a) // 1
var a = 2
console.log(a) // 2

let、const 的特点

  • 不支持变量声明预解析 (先声明后使用)
  • 支持块级作用域
  • 不能重复声明 (暂时性死区)

{
console.log(a) // Uncaught ReferenceError: a is not defined
let a = 1
const b = 2
}
console.log(b) // Uncaught ReferenceError: b is not defined
let c
let c = 3 // Uncaught SyntaxError: Identifier 'c' has already been declared

不支持变量预解析和不支持变量的重复声明大大地提高了数据使用的安全性。所以 var 能不用则不用。

在实际开发中这种方法在日益普及:默认使用 const,只有当确实需要改变变量的值的时候才使用 let。因为大部分的变量的值在初始化后不应再改变。

const 常量

  • 声明时就必须初始化
  • 初识化后就不能改变
const a // Uncaught SyntaxError: Missing initializer in const declaration

const b = 1
b = 2 // Uncaught TypeError: Assignment to constant variable.

const 在存对象的时候,存的是对象的引用(地址),引用不能改变,但对象的内容却可以。

const p = {
name: '张三',
age: 20
}
p.age = 21
console.log(p.age) // 21

如果想让对象的属性也不能改变,可以使用 Object.freeze() 方法冻结对象的一层属性

const p = Object.freeze({
name: '张三',
age: 20
})

console.log(Object.isFrozen(p)) // true
p.age = 21
console.log(p.age) // 20

但是如果对象中还有对象,就不行了,只能递归冻结。

解构赋值

按照一定模式,从数组和对象中提取值,并对变量进行赋值。

数组解构

  • 顺序对应
let [a, b, c] = [1, 2, 3]
let [d, , e] = [4, 5, 6]
console.log(a, b, c, d, e) // 1 2 3 4 6

交换数字:

let a = 1
let b = 2

;[a, b] = [b, a]
console.log(a, b) // 2 1

a = a ^ b
b = a ^ b
a = a ^ b
console.log(a, b) // 1 2

a = a + b
b = a - b
a = a - b
console.log(a, b) // 2 1

字符串解构

  • 和数组一样
    let [char1, char2, char3] = 'abcd'
    console.log(char1, char2, char3) // a b c

对象解构

  • 变量名要与 key 对应
  • 无顺序
let {a, b, c} = {a: 'aa', b: 'bb'}
console.log(a, b, c) // aa bb undefined

别名

在对象解构中,可以自定义变量名

let {a: name, b: age} = {a: '张三', b: 20}
console.log(name, age) // 张三 20
console.log(a) // Uncaught ReferenceError: a is not defined

多重解构

let {foo: [a, b]} = {foo:[1, 2], bar: 'bbb'}
console.log(a, b) // 1 2

展开运算符 …

把数组或对象展开成参数序列

[‘a’, ‘b’, ‘c’] —> ‘a’, ‘b’, ‘c’
{height: 100, width: 200} —> height:100, width: 200

  • 在调用函数传参时,可以将传入的数组转换成参数序列
console.log(Math.max(1, 2, 3))  // 3

var arr = [1, 2, 3]
console.log(Math.max(...arr)) // 3

// => Math.max(arr[0], arr[1], arr[2])
  • 数组、对象合并
var arr1 = [1, 2, 3]
var arr2 = ['a','b']
var arr3 = [...arr1, ...arr2]
console.log(arr3) // [1, 2, 3, "a", "b"]

var obj1 = {width: 100, height: 200}
var obj2 = {top: 123, width: 200}
var obj3 = {...obj1, ...obj2}
console.log(obj3) // {width: 200, height: 200, top: 123}

var obj4 = {...arr1, ...obj1}
console.log(obj4) // {0: 1, 1: 2, 2: 3, width: 100, height: 200}

var arr4 = [...arr1, ...obj1] // Uncaught TypeError: obj1 is not iterable
  • 将任意可迭代对象转换成数组
var str = 'abcde'
var arr1 = [...str]
console.log(arr1) // ["a", "b", "c", "d", "e"]

模板字符串

  • 保持内容格式
var str = `
<ul>
<li></li>
</ul>
`
console.log(str)
/*
<ul>
<li></li>
</ul>
*/
  • 变量表达式解析 ${ }
var a = 1
var str = `<p>${a + 123}<p>`
console.log(str) // <p>124<p>

字面量语法扩展

属性初始值简写

字面量集合中如果键和值名称相同,可以只写键。

function createPerson(name, age) {
return {
name: name,
age: age
}
}

// 简写:
function createPerson(name, age) {
return {
name,
age
}
}

对象方法的简写

可以省略 :function 关键字

var person = {
name: 'nick',
sayName: function() {
console.log(this.name)
}
}

// 简写:
var person = {
name: 'nick',
sayName() {
console.log(this.name)
}
}

迭代器

在标准 for 循环中,通过定义索引变量来追踪当前元素,很简单,但是,当涉及到多重 for 循环嵌套时,代码复杂度增大,容易出错(误用其他循环的索引变量)。迭代器的出现旨在消除这种复杂性。

什么是迭代器

迭代器就是一个具有 next() 方法的对象,每次调用 next() 都会返回一个结果对象,结果对象有两个属性, value 表示当前值,done 表示遍历是否结束。

根据上面的规则,我们创建一个迭代器的工厂:

function createIterator(items){
var i = 0
return {
next() {
var done = i >= items.length
var value = !done ? items[i++] : undefined

return {
value,
done
}
}
}
}

var iterator = createIterator([1, 2, 'bar'])
console.log(iterator.next()) // {value: 1, done: false}
console.log(iterator.next()) // {value: 2, done: false}
console.log(iterator.next()) // {value: "bar", done: false}

for of

除了迭代器以外,我们还需要一个可以遍历迭代器对象的方式,ES6 提供了 for (var value of iterator) 语句。

我们先试着遍历前面自己写的迭代器对象:

var iterator = createIterator([1, 2, 'bar'])
for (var value of iterator) {
console.log(value)
}

// Uncaught TypeError: iterator is not iterable

我们写的迭代器对象竟然不能被迭代!??

其实我们还差一步,因为 for(A of B) 语句其实是通过调用对象 B 的 Symbol.iterator 方法来获取迭代器,随后调用迭代器的 next() 方法,将其返回的结果对象的 value 属性赋给变量 A。

所以:

var b = {}
b[Symbol.iterator] = function() {
return createIterator([1, 2, 'bar'])
}

for (var a of b) {
console.log(a)
}

// 1
// 2
// bar

默认可迭代对象

我们试试直接用 for of 遍历数组

var arr = ['a', 'b', 'c']
for (var v of arr) {
console.log(arr)
}

// a
// b
// c

尽管我们没有手动去添加 arr 的 ‘Symbol.iterator’ 属性,但是还是可以遍历,这是因为 ES6 为数组默认部署了 Symbol.iterator ,我们也可以重写这个方法。

var arr = ['a', 'b', 'c']
arr[Symbol.iterator] = function() {
return createIterator(['aa', 'bb', 'cc'])
}
for (var v of arr) {
console.log(v)
}
// aa
// bb
// cc

除了数组外,还有一些数据结构默认部署了 Symbol.iterator 属性。

默认可以迭代的对象:

  • 数组
  • 字符串
  • Set
  • Map
  • 类数组对象,如 arguments,DOM 里的 NodeList
  • Generator 对象

箭头函数

(参数) => {函数体}

// 只有一个参数可以不写 ()
x => {console.log(x)}

// 函数体中只有一条语句可以不写 {} ,该条语句的结果会作为函数的返回值
var foo = x => x*x
console.log(foo(2)) // 4

  • 内部不绑定 this ,访问外部作用域的 this 。也就是说箭头函数中的 this 在箭头函数定义时就已经确定了。
  • 内部没由 arguments
  • 不能作为构造函数
  • 不能作为生成器函数

Set

集合:类似于数组,但成员的值都是唯一的,没有重复的值,并且无序。
Set() 是一个构造函数,可以传入一个可迭代对象初始化默认值

数组去重:

var arr = [1, 2, 3, 1, 2]
arr = [...new Set(arr)]
console.log(arr) // [1, 2, 3]

Map

映射:
类似于对象,但是 key 值可以是任意类型的值,而不仅仅局限于字符串。

WeakMap

类似 Map ,但 key 必须是对象,并且对 key 这个对象是弱引用的(GC 机制会回收只有弱引用或没有引用的对象)。

可以优化类中私有化属性使用 Map 造成的强引用不能释放问题。

class

class Person {
constructor(name, age) {
this.name = name
this.age = age
}

sayHi() {
console.log(this.name + ': Hi~')
}
}

var p = new Person('张三', 23)
console.log(p) // Person {name: "张三", age: 23}
p.sayHi() // 张三: Hi~

class 的继承写法:

class Student extends Person {
constructor(name, gender) {
super(name) // 子类有 constructor 就必须要有 super() 不然报错
this.gender = gender
}
}

var s = new Student('小明', '男')
console.log(s) // Student {name: "小明", age: undefined, gender: "男"}
s.sayHi() // 小明: Hi~
© 2019 墨夜 All Rights Reserved.
Theme by hiero