# ECMAScript 6

ECMAScript6简称ES6,2015年推出,相比ES5在保证向下兼容的前提下,提供大量新特性。

  1. 块级作用域 关键字let, 常量const
  2. 对象字面量的属性赋值简写(property value shorthand

# 1) 定义变量

# 前述

  1. var function存在变量提声
  2. var 只会提前声明
  3. function 即声明又定义
  4. 全局作用域下使用varfunction声明的变量会给window增加属性
console.info(num) // undefined
console.info(getNum) // 将当前函数打印
var num = 1
function getNum(){
    
}
console.info(window.num) // 1
console.info('getNum' in window) // true
1
2
3
4
5
6
7
8

# let

  1. 没有变量提声
  2. 不可以重复声明
  3. 不会给window增加属性
  4. 在块级作用域下他是私有的
// num is not defined
// console.info(num) // 报错 没有变量提声
let num = 1
// let num = 2 // 报错 不可重复声明
// window 下没有 num 属性
console.info(window.num) // undefined
1
2
3
4
5
6

# const

  1. 没有变量提声
  2. 不可以重复声明
  3. 不会给window增加属性
  4. const定义变量,一旦声明必须赋值
  5. const定义的是一个常量,不可以重复赋值
// const a; // 报错 一旦声明必须赋值
const b = 1
// b = 3 报错 不可重复赋值
1
2
3

# 2) 块级作用域

# 描述

  1. 一个大括号{}就是一个块级作用域。
  2. let/const受块级作用域影响。
{
	var a = 0
	let b = 1
	function getA(){
		console.info("test")
	}
	console.info('a=' + a)
	console.info(getA)
}
console.info('块级外a=' + a)
// console.info('块级外b=' + b) // 报错
1
2
3
4
5
6
7
8
9
10
11

# 常见的块级作用域

if/for/对象....

常见问题

在这些语法中的块级作用域下的函数function只会提声不会定义

console.info(getB) //undefined
if (true) {
    // 同级就拿到了
	console.info(getB)
	function getB(){
		
	}
}
1
2
3
4
5
6
7
8

# 3) 解构赋值

ES6中提供了很多新的特性其中就包括数组解构赋值的特性,在代码上有一定的优化程度。

# 数组解构赋值

常规解构赋值

let arr = [1,2,3,4]
// let x = arr[0]
// let y = arr[1]
// let m = arr[2]
// let n = arr[3]
// 上述繁琐写法,下列直接进行解构赋值
let [x,y,m,n] = arr
console.info(x,y,m,n) // 1 2 3 4
1
2
3
4
5
6
7
8

解构设置默认值

// 注意:如下例所示 当x3等于undefined时,才会执行默认赋值。
let [x1,x2,x3 = 10] = [1,2]
console.info(x1,x2,x3) // 1 2 10

1
2
3
4

解构设置自执行函数

// 注意:如下例所示,当m3等于undefined时,才会执行自执行函数。
let [m1,m2,m3 = (function (){
    console.info("进入了自执行函数")
    return 22
})] = [1,2]
console.info(m1,m2,m3) // 1 2 22
1
2
3
4
5
6

省略解构赋值

let [,,a3] = [1,2,3]
console.info(a3) // 3
1
2

不定参数解构赋值

// 将后面的项放在一个数组中赋值给y3
// 扩展运算符 ...
let [y1,y2,...y3] = [1,2,3,4,5]
console.info(y1,y2,y3) // 1 2 [3,4,5]

1
2
3
4
5

# 对象解构赋值

常规解构赋值

// 如果变量名和属性名一样则可以直接省略
// let {name: name,age: age} = {name: '程序员',age: 22}
let {name,age} = {name: '程序员',age: 22}
console.info(name,age) // 程序员 22
1
2
3
4

设置解构默认值

let {name,age = 10} = {name: '学生'}
console.info(name,age)
1
2

解构设置自执行函数

let {name,age = (function(){
    console.info("进入对象自执行函数")
    return 12
})} = {name: '教师'}
console.info(name,age)
1
2
3
4
5

对象中其他类型赋值

let {name,age,hobby:[x1,x2,x3 = '未设置']} = {name:'青少年',age: 15,hobby:['打篮球','打游戏']}
console.info(name,age,x1,x2,x3) // 青少年 15 打篮球 打游戏 未设置
1
2

# 其他解构赋值

日常中可能会出现这么一串代码

let [x,y] = '123'
// 这样也可以解构,他会将字符串解析成数组的形式在进行赋值。
console.info(x,y) // 1 2

// 又比如对象的奇怪解构
let {__proto__:a} = 1
console.info(a) // 输出一个Number对象

// 又比如这样
console.info(Object("1234"))
// 我们可以看到这个1234转对象后有个length属性,我们可以直接将length解构出来赋值给b
let {length:b} = "1234"
console.info(b) // 输出 4
1
2
3
4
5
6
7
8
9
10
11
12
13

# 函数解构赋值

基本语法

// 基本语法
function getA1({name = "无名氏",age = 100}){
    console.info(name,age)
}
// getA1() // 报错,因为不能接收空的参数
getA1({}) // 无名氏 100

// 避免getA1()不传参报错问题
function getA2({name='hanshu',age=100} = {}){
    console.info(name,age)
}
getA2() // 无名氏 100
1
2
3
4
5
6
7
8
9
10
11
12

下面这两种写法结果是不一样的。

// 避免getA1()不传参报错问题
function getA1({name='hanshu',age=100} = {}){
    console.info(name,age)
}

function getA2({name,age} = {name: 'hanshu',age: 100}){
    console.info(name,age)
}
// 因为`getA1`传参时,则他会进行默认赋值。所以输出 hanshu 100
getA1() // hanshu 100
// 而`getA2`传参时,已经将空值赋给`name`和`age`了,所以会输出`undefined`
getA2() // undefined undefined
1
2
3
4
5
6
7
8
9
10
11
12

# 4) ES6中字符串的扩展

ES6中对字符串的方法进行了一些新的扩展,包括ES6中的模板字符串的用法。

# 字符串原型上的扩展方法

console.info(String.prototype)
1

image


# includes

includes() 方法用于判断字符串是否包含指定的子字符串。

如果找到匹配的字符串则返回 true,否则返回false

注意: includes() 方法区分大小写。

语法

string.includes(searchvalue, start)

参数值 描述
searchvalue 必需,要查找的字符串。
start 可选,设置从那个位置开始查找,默认为 0。

示例

var str = "Hello world, welcome to the Runoob.";
var n = str.includes("world", 12);
console.info(n) // false
1
2
3

# startsWith/endWith

定义和用法

  1. startsWith() 方法用于检测字符串是否以指定的子字符串开始。
  2. 如果是以指定的子字符串开头返回 true,否则 false
  3. startsWith() 方法对大小写敏感。
  4. endWith()startsWith() 正好相反。

语法

string.startsWith(searchvalue, start)

参数值 描述
searchvalue 必需,要查找的字符串。
start 可选,查找的开始位置,默认为 0。

示例

var str = "Hello world, welcome to the Runoob.";
var n = str.startsWith("world", 6);
console.info(n) // true
1
2
3

# 5) ES6中数组的扩展

ES6中对数组的方法进行了一些新的扩展,下面是一些较为常用的方法整合。

# 数组原型的扩展方法

console.info(Array.prototype)
1

image


# copyWithin

定义和用法

copyWithin() 方法用于从数组的指定位置拷贝元素到数组的另一个指定位置中。

语法

array.copyWithin(target, start, end)

参数 描述
target 必需。目标的复制起始位置
start 可选。复制的下标起始位置
end 可选。复制的下标结束位置,负数为倒数。默认为(Array.length)

示例

let ary = [1,2,3,4,5,6,7,8]
// 示例
// 参数(替换的目标起始位置,查找的起始位置,查找的结束位置)
ary.copyWithin(4,2,4)
console.info(ary) // 1 2 3 4 3 4 7 8
// 省略最后的参数默认查找后面全部
ary.copyWithin(3,2)
console.info(ary) // 1 2 3 3 4 3 4 7
1
2
3
4
5
6
7
8

# fill

定义和用法

fill() 方法用于将一个固定值替换数组的元素。

语法

array.fill(value, start, end)

参数 描述
value 必需。填充的值。
start 可选。开始填充位置。
end 可选。停止填充位置 (默认为 array.length)

示例

var fruits = ["Banana", "Orange", "Apple", "Mango"];
fruits.fill("Runoob", 2, 4);
console.info(fruits) // Banana,Orange,Runoob,Runoob
1
2
3

# filter

定义和用法

filter() 方法创建一个新的数组,新数组中的元素是通过检查指定数组中符合条件的所有元素。简称过滤数组

注意: filter() 不会对空数组进行检测。

注意: filter() 不会改变原始数组。

语法

array.filter(function(currentValue,index,arr), thisValue)

参数说明

参数 描述
函数 function(currentValue, index,arr) 详见下方函数参数
thisValue 可选。对象作为该执行回调时使用,传递给函数,用作 "this" 的值。如果省略了 thisValue ,"this" 的值为 "undefined"
函数参数 描述
currentValue 必须。当前元素的值
index 可选。当前元素的索引值
arr 可选。当前元素属于的数组对象

示例(1)

let arry = ['管理员',3,4,11,'成员']
let arr = arry.filter(function(item,index){
    return typeof item === 'string'
})
console.info(arr) // 管理员 成员
1
2
3
4
5

示例(2)

<p>最小年龄: <input type="number" id="ageToCheck" value="18"></p>
<button onclick="myFunction()">点我</button>

<p>所有大于指定数组的元素有? <span id="demo"></span></p>

<script>
var ages = [32, 33, 12, 40];

function checkAdult(age) {
    return age >= document.getElementById("ageToCheck").value;
}

function myFunction() {
    document.getElementById("demo").innerHTML = ages.filter(checkAdult);
}
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# find

定义和用法

先遍历数组,当函数返回的结果为true时停止遍历,返回当前项的值。

语法

array.find(function(currentValue, index, arr),thisValue)

参数说明

参数 描述
函数 function(currentValue, index,arr) 详见下方函数参数
thisValue 可选。 传递给函数的值一般用 "this" 值。如果这个参数为空, "undefined" 会传递给 "this" 值
函数参数 描述
currentValue 必须。当前元素
index 可选。当前元素的索引值
arr 可选。当前元素属于的数组对象

示例 (1)

var ages = [4, 12, 16, 20];
 
function checkAdult(age) {
    return age >= document.getElementById("ageToCheck").value;
}
 
function myFunction() {
    document.getElementById("demo").innerHTML = ages.find(checkAdult);
}
1
2
3
4
5
6
7
8
9

示例 (2)

const arr = ['管理员','产品经理','CTO首席技术官','技术总监','码农']
let str = arr.find(function(item,index){
	return item.includes('CTO首席技术官')
})
console.info(str) // CTO首席技术官
1
2
3
4
5

# findIndex

定义和用法

findIndex() 方法返回传入一个测试条件(函数)符合条件的数组第一个元素位置。

findIndex() 方法为数组中的每个元素都调用一次函数执行:

  • 当数组中的元素在测试条件时返回 true 时, findIndex() 返回符合条件的元素的索引位置,之后的值不会再调用执行函数。

  • 如果没有符合条件的元素返回 -1

注意: findIndex() 对于空数组,函数是不会执行的。

注意: findIndex() 并没有改变数组的原始值。

语法

array.findIndex(function(currentValue, index, arr), thisValue)

参数说明

参数 描述
函数 function(currentValue, index,arr) 详见下方函数参数
thisValue 可选。 传递给函数的值一般用 "this" 值。如果这个参数为空, "undefined" 会传递给 "this" 值
函数参数 描述
currentValue 必须。当前元素
index 可选。当前元素的索引
arr 可选。当前元素属于的数组对象

示例 (1)

var ages = [4, 12, 16, 20];
 
function checkAdult(age) {
    return age >= document.getElementById("ageToCheck").value;
}
 
function myFunction() {
    document.getElementById("demo").innerHTML = ages.findIndex(checkAdult);
}
1
2
3
4
5
6
7
8
9

示例 (2)

const arr = ['管理员','产品经理','CTO首席技术官','技术总监','码农']
let index = arr.findIndex(function(item,index){
	return item.includes('CTO首席技术官')
})
console.info(index) // 2
1
2
3
4
5

# includes

定义和用法

includes() 方法用来判断一个数组是否包含一个指定的值,如果是返回 true,否则false。

语法

arr.includes(searchElement)

arr.includes(searchElement, fromIndex)

参数说明

参数 描述
searchElement 必须。需要查找的元素值。
fromIndex 可选。从该索引处开始查找 searchElement。如果为负值,则按升序从 array.length + fromIndex 的索引开始搜索。默认为 0。

示例 (1) [如果fromIndex 大于等于数组长度 ,则返回 false 。该数组不会被搜索]

var arr = ['a', 'b', 'c'];
arr.includes('c', 3);   //false
arr.includes('c', 100); // false
1
2
3

示例 (2)

let arr = ['a','b','c']
// 查找数组中是否包含'c'
console.info(arr.includes('c')) // true
// 从下标1开始查找'c'
console.info(arr.includes('c',1)) // true
1
2
3
4
5

# every

定义和用法

every() 方法用于检测数组所有元素是否都符合指定条件(通过函数提供)。

every() 方法使用指定函数检测数组中的所有元素:

  • 如果数组中检测到有一个元素不满足,则整个表达式返回 false ,且剩余的元素不会再进行检测。
  • 如果所有元素都满足条件,则返回 true。

注意: every() 不会对空数组进行检测。

注意: every() 不会改变原始数组。

语法

array.every(function(currentValue,index,arr), thisValue)

参数说明

参数 描述
函数 function(currentValue, index,arr) 详见下方函数参数
thisValue 可选。对象作为该执行回调时使用,传递给函数,用作 "this" 的值。如果省略了 thisValue ,"this" 的值为 "undefined"
函数参数 描述
currentValue 必须。当前元素的值
index 可选。当前元素的索引值
arr 可选。当前元素属于的数组对象

示例 (1) [检测数组 ages 的所有元素是否都大于等于输入框中指定的数字]

<p>最小年龄: <input type="number" id="ageToCheck" value="18"></p>
<button onclick="myFunction()">点我</button>

<p>是否所有年龄都符号条件? <span id="demo"></span></p>

<script>
    var ages = [32, 33, 12, 40];
    
    function checkAdult(age) {
        return age >= document.getElementById("ageToCheck").value;
    }
    
    function myFunction() {
        document.getElementById("demo").innerHTML = ages.every(checkAdult);
    }
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

示例 (2)

let arr = ['a','b',1,'c']
let pd = arr.every(function(item,index){
    // 检测是否全部是string类型
    return typeof item === 'string'
})
console.info(pd) // false
1
2
3
4
5
6

# some

定义和用法

some() 方法用于检测数组中的元素是否满足指定条件(函数提供)。

some() 方法会依次执行数组的每个元素:

  • 如果有一个元素满足条件,则表达式返回true , 剩余的元素不会再执行检测。
  • 如果没有满足条件的元素,则返回false。

注意: some() 不会对空数组进行检测。

注意: some() 不会改变原始数组。

语法

array.some(function(currentValue,index,arr),thisValue)

参数说明

参数 描述
函数 function(currentValue, index,arr) 详见下方函数参数
thisValue 可选。对象作为该执行回调时使用,传递给函数,用作 "this" 的值。如果省略了 thisValue ,"this" 的值为 "undefined"
函数参数 描述
currentValue 必须。当前元素的值
index 可选。当前元素的索引值
arr 可选。当前元素属于的数组对象

示例 (1) [检测数组 ages 中是否有元素大于输入框输入的值]

<p>最小年龄: <input type="number" id="ageToCheck" value="18"></p>
<button onclick="myFunction()">点我</button>

<p>判断结果: <span id="demo"></span></p>

<script>
    var ages = [4, 12, 16, 20];
    
    function checkAdult(age) {
        return age >= document.getElementById("ageToCheck").value;
    }
    
    function myFunction() {
        document.getElementById("demo").innerHTML = ages.some(checkAdult);
    }
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

示例 (2)

const arr = ['我爱罗','宇智波鼬','宇智波斑']
let pd = arr.some(function (item,index){
    return item.includes('宇智波鼬')
})
console.info(pd) // true
1
2
3
4
5

# 数组的空位

在ES5中对于数组中的空位一般直接跳过

在ES6中对于数组中的空位处理为undefined

// ES5
let arr1 = [1,2,,,,3]
console.info("ES5-filter")
arr1.filter(function (item){
 console.info(item)
})
// ES6
console.info("ES6-find")
arr1.find(function (item){
    console.info(item)
})

// ES6
console.info("ES6-for of")
for (let item of arr1){
    console.info(item)
}

// ES5
console.info("ES5-for in")
for (let key in arr1){
    console.info(key)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

# 6) ES6中函数的扩展

# 函数参数

// 参数默认值
function fn (x = '浅梦',y = 'ES6'){
    console.info(x,y)
}
fn(0) // 0 ES6

// 参数使用解构赋值
function fn1 ({name = '浅梦', age = 100} = {}){
    console.info(name,age)
}
fn1() // 浅梦 100

function fn2 ({name, age} = {name:'浅梦',age:100}){
    console.info(name,age)
}
fn2({}) // undefined undefined

// length 属性 ,形参的个数
function fn3 (x=1,y=10){

}
// length只计算没有默认值的形参
console.info(fn3.length) // 0

// 参数默认值位置:一般参数的默认值都放在后置位
function fu4 (x=10,y=20){

}
// fn3(,1) 会报错

function fn5 (...args){
	// arguments 实参集合 类数组
	console.info(arguments)
	console.info(args)
}
fn5(1,2,3,4,5) //(5) [1, 2, 3, 4, 5]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37

# 函数名字

function fn (){
    
}
console.info(fn.name) // fn
console.info((function () {}).name) // ''
// 特殊情况
// 1. 通过bind方法得到一个新的函数 name是bound fn
let fn1 = fn.bind(null)
console.info(fn1.name) // bound fn

// 2. 通过构造函数方式创建一个函数 名字都叫做 'anonymous'
// new Function ()
// function fn(形参) {函数体}
let fn2 = new Function('x,y','console.info(x,y);return x + y')
fn2(10,100) // 110
console.info(fn2.name) // anonymous

// 通过new Function() 创建json对象,下方有一个json字符串。
let str = '[{"name":"qm"},{"age":100}]'
let arr = (new Function('return' + str))()
console.info(arr) // 0: {name: "qm"} 1: {age: 100}
// 上方的new Function用法相当于下面的的方式
// function jsonFn(){
// 	return [{"name":"qm"},{"age":100}]
// }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

# 函数作用域问题

// 函数执行的时候闲给形参赋值
// 形参也是私有变量
// 函数中的默认值赋值受作用域影响
let x = 100
function fn(x, y = x){
	console.info(y)
}
console.info(1) // 1
1
2
3
4
5
6
7
8

上方通过new Function方式创建函数较为少用,了解即可。

# 7) 扩展运算符和箭头函数

# 扩展运算符

非数组和数组的相互转换(类数组)

有length的是类数组

let str = '123'
console.info([...str]) // (3) ["1", "2", "3"]
1
2
<ul>
	<li></li>
	<li></li>
	<li></li>
	<li></li>
	<li></li>
</ul>
<script>
console.info([...document.getElementsByTagName("li")])
</script>
1
2
3
4
5
6
7
8
9
10

# 箭头函数

箭头函数是匿名函数

// let fn = (形参) => {函数体}

let fn = x => x + 1
// 加入函数体只有一行代码 return 可以省略 {} 和 return
// 上述代码等同于 let fn = x => { return x + 1 }
// 通常函数当做参数的时候使用箭头函数。
let ary["qm",1,2,3]
// 形参只有一个的时候可以省略小括号
// ary.filter((item)=>{})
ary.filter((item)=> typeof item == "number") // 1 2 3
// 

1
2
3
4
5
6
7
8
9
10
11
12

箭头函数的几个特点

  1. 箭头函数没有this指向 里面的this是上一级作用域下的this
let obj = {
    fn:function () { 
        let f = () => {
            console.info(this)
        }
        f()
    }
}
obj.fn()
1
2
3
4
5
6
7
8
9
  1. 箭头函数没有 arguments
let fn1 = (...arg) => {
    // console.info(arguments)
    console.info(arg)
}
fn1(1,2,3,4) // 1 2 3 4
1
2
3
4
5
  1. 箭头函数不可以用作构造函数,因为不可以使用new执行
function FF() {}
new FF
// let F = () => {new F} 
// is not a constructor
1
2
3
4

# 8) 对象拓展

# 对象的简洁方法

let name = "qm" , age = 100
let school = {name,age} // let school = {name:name,age:age}

// 比如动态key
let str = 'name'
let obj = {
    fn(){},
    // 属性名用中括号包裹表示他是一个变量。
    [str]:name,
    ['my' + str]:name
    
}
1
2
3
4
5
6
7
8
9
10
11
12

# Object的方法扩展

// Object() 将参数变成对象
console.info(Object(1))
1
2

# Object.is

判断两个值是否相等

一般情况下,我们常使用 === 进行对比。

但是他会存在一些问题,比如我们都知道NaN 和 NaN 不等。-0 === 0 得到的结果是true。

以上问题在ES6中得到友好的解决。

console.info(NaN === NaN) // false
console.info(-0 === 0) // true

Object.is(NaN,NaN) // true
Object.is(-0,0) // false
1
2
3
4
5

# Object.assign

合并对象

let obj1 = {name:'浅梦'}
let obj2 = {age:10}
let tempObj = Object.assign(obj1,obj2)
console.info(obj1) // {name: "浅梦", age: 10}
console.info(tempObj) // {name: "浅梦", age: 10}
// 下面是ES7的语法
let school = {...obj1,...obj2}
console.info(school)
1
2
3
4
5
6
7
8

如上所示,将obj2合并到obj1中,并返回obj1。 obj1已经进行了合并,所以可以不写返回值。

# Object.getOwnPropertyDescriptor

获取一个对象某个属性的描述

Object.getOwnPropertyDescriptor('123',length)
// 输出 {value: "1", writable: false, enumerable: true, configurable: false}
// 字符串的length是不可修改的。所以writable是false。
let str = '333'
str.length = 0
console.info(str.length) // 3

1
2
3
4
5
6
7
  • configurable 是否可配置(可删除数据)
  • enumerable 是否可枚举
  • value
  • writable 是否可修改

# Object.keys

返回数组,为对象的key名数组[所有可枚举的属性]

let obj = {name:'浅梦',age:27}
console.info(Object.keys(obj))
// 打印 ['name','age']
1
2
3

# Object.values

返回数组,为对象的value值数组[所有可枚举的属性]

let obj = {name:'浅梦',age:27}
console.info(Object.values(obj))
// 打印 ['浅梦',27]
1
2
3

# Object.entries

返回一个二维数组,包含该对象的属性名和属性值。

let obj = {name:'浅梦',age:27}
console.info(Object.entries(obj))
// 打印[array(2),array(2)]
//  > 0 (2) ['name','浅梦']
//  > 1 (2) ['age',27]
1
2
3
4
5

# 对象的set和get

let obj = {
	name:'AA',
	get getName(){
		console.info('触发了getNam函数')
		return this.name
	},
	set setName(val){
		// 只要是通过obj给name属性设置值就会触发该函数
		console.info('触发了setName函数')
		// val 设置的值
		this.name = val
		return 
	}
}
console.info(obj.getName)
obj.setName = '浅梦'
console.info(obj.getName)
// 触发了get getName函数
// AA
// 触发了set setName函数
// 触发了get getName函数
// 浅梦
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

# 9) 基本数据类型Symbol

  • 新的基本数据类型
  • 执行Symbol函数返回一个symbol数据类型
  • 和字符串类似。
  • 任何一个symbol数据都不相等。
  • symbol 可以接收一个参数(),参数是对这个symbol数据的描述。
  • 一般当做对象的属性
  • 不可以转数字
  • 不可以跟其他值计算
  • 不可以字符串拼接
  • 可以转布尔值
  • 并不常用
let sym1 = Symbol('这是sym') // 得到一个Symbol数据类型
let sym2 = Symbol('这是sym')
console.info(typeof sym1) // 打印 symbol
console.info(sym1 == sym2) // false
1
2
3
4

# 10) Proxy对象代理

Proxy 用于修改某些操作的默认行为,等同于在语言层面做出修改,所以属于一种“元编程”(meta programming),即对编程语言进行编程。

Proxy 可以理解成,在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。Proxy 这个词的原意是代理,用在这里表示由它来“代理”某些操作,可以译为“代理器”。

var obj = new Proxy({}, {
  get: function (target, key, receiver) {
    console.log(`getting ${key}!`);
    return Reflect.get(target, key, receiver);
  },
  set: function (target, key, value, receiver) {
    console.log(`setting ${key}!`);
    return Reflect.set(target, key, value, receiver);
  }
});

1
2
3
4
5
6
7
8
9
10
11

上面代码对一个空对象架设了一层拦截,重定义了属性的读取(get)和设置(set)行为。这里暂时先不解释具体的语法,只看运行结果。对设置了拦截行为的对象obj,去读写它的属性,就会得到下面的结果。

obj.count = 1
//  setting count!
++obj.count
//  getting count!
//  setting count!
//  2
1
2
3
4
5
6

上面代码说明,Proxy 实际上重载(overload)了点运算符,即用自己的定义覆盖了语言的原始定义。

ES6 原生提供 Proxy 构造函数,用来生成 Proxy 实例。

var proxy = new Proxy(target, handler);
1

Proxy 对象的所有用法,都是上面这种形式,不同的只是handler参数的写法。其中,new Proxy()表示生成一个Proxy实例,target参数表示所要拦截的目标对象,handler参数也是一个对象,用来定制拦截行为。

下面是另一个拦截读取属性行为的例子。

var proxy = new Proxy({}, {
  get: function(target, property) {
    return 35;
  }
});

proxy.time // 35
proxy.name // 35
proxy.title // 35
1
2
3
4
5
6
7
8
9

上面代码中,作为构造函数,Proxy接受两个参数。第一个参数是所要代理的目标对象(上例是一个空对象),即如果没有Proxy的介入,操作原来要访问的就是这个对象;第二个参数是一个配置对象,对于每一个被代理的操作,需要提供一个对应的处理函数,该函数将拦截对应的操作。比如,上面代码中,配置对象有一个get方法,用来拦截对目标对象属性的访问请求。get方法的两个参数分别是目标对象和所要访问的属性。可以看到,由于拦截函数总是返回35,所以访问任何属性都得到35。

注意,要使得Proxy起作用,必须针对Proxy实例(上例是proxy对象)进行操作,而不是针对目标对象(上例是空对象)进行操作。

如果handler没有设置任何拦截,那就等同于直接通向原对象。

var target = {};
var handler = {};
var proxy = new Proxy(target, handler);
proxy.a = 'b';
target.a // "b"
1
2
3
4
5

上面代码中,handler是一个空对象,没有任何拦截效果,访问proxy就等同于访问target

一个技巧是将 Proxy 对象,设置到object.proxy属性,从而可以在object对象上调用。

var object = { proxy: new Proxy(target, handler) };
1

Proxy 实例也可以作为其他对象的原型对象。

var proxy = new Proxy({}, {
  get: function(target, property) {
    return 35;
  }
});

let obj = Object.create(proxy);
obj.time // 35
1
2
3
4
5
6
7
8

上面代码中,proxy对象是obj对象的原型,obj对象本身并没有time属性,所以根据原型链,会在proxy对象上读取该属性,导致被拦截。

同一个拦截器函数,可以设置拦截多个操作。

var handler = {
  get: function(target, name) {
    if (name === 'prototype') {
      return Object.prototype;
    }
    return 'Hello, ' + name;
  },

  apply: function(target, thisBinding, args) {
    return args[0];
  },

  construct: function(target, args) {
    return {value: args[1]};
  }
};

var fproxy = new Proxy(function(x, y) {
  return x + y;
}, handler);

fproxy(1, 2) // 1
new fproxy(1, 2) // {value: 2}
fproxy.prototype === Object.prototype // true
fproxy.foo === "Hello, foo" // true
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

对于可以设置、但没有设置拦截的操作,则直接落在目标对象上,按照原先的方式产生结果。

下面是 Proxy 支持的拦截操作一览,一共 13 种。

  • get(target, propKey, receiver):拦截对象属性的读取,比如proxy.foo和proxy['foo']
  • set(target, propKey, value, receiver):拦截对象属性的设置,比如proxy.foo = vproxy['foo'] = v,返回一个布尔值。
  • has(target, propKey):拦截propKey in proxy的操作,返回一个布尔值。
  • deleteProperty(target, propKey):拦截delete proxy[propKey]的操作,返回一个布尔值。
  • ownKeys(target):拦截Object.getOwnPropertyNames(proxy)Object.getOwnPropertySymbols(proxy)Object.keys(proxy)for...in循环,返回一个数组。该方法返回目标对象所有自身的属性的属性名,而Object.keys()的返回结果仅包括目标对象自身的可遍历属性。
  • getOwnPropertyDescriptor(target, propKey):拦截Object.getOwnPropertyDescriptor(proxy, propKey),返回属性的描述对象。
  • defineProperty(target, propKey, propDesc):拦截Object.defineProperty(proxy, propKey, propDesc)Object.defineProperties(proxy, propDescs),返回一个布尔值。
  • preventExtensions(target):拦截Object.preventExtensions(proxy),返回一个布尔值。
  • getPrototypeOf(target):拦截Object.getPrototypeOf(proxy),返回一个对象。
  • isExtensible(target):拦截Object.isExtensible(proxy),返回一个布尔值。
  • setPrototypeOf(target, proto):拦截Object.setPrototypeOf(proxy, proto),返回一个布尔值。如果目标对象是函数,那么还有两种额外操作可以拦截。
  • apply(target, object, args):拦截 Proxy 实例作为函数调用的操作,比如proxy(...args)proxy.call(object, ...args)proxy.apply(...)
  • construct(target, args):拦截 Proxy 实例作为构造函数调用的操作,比如new proxy(...args)

# ES6-Proxy实例方法的详细介绍 (opens new window)

# Class表达式

# class

ES6中引入了class类的概念,和Java语法类似,基本上ES6中的class可以看做是一种语法支持,它的绝大部分原理ES5中都可以做到,新的class写法只是让对象原型的写法更加清晰,更像面向对象编程语法而已 (更像Java语法)

  • 比如下面所示:

ES5

function Point(x, y) {
  this.x = x;
  this.y = y;
}

Point.prototype.toString = function () {
  return '(' + this.x + ', ' + this.y + ')';
};

var p = new Point(1, 2);
1
2
3
4
5
6
7
8
9
10

ES6

class Point {
  constructor(x, y) {
    this.x = x;
    this.y = y;
  }

  toString() {
    return '(' + this.x + ', ' + this.y + ')';
  }
}
1
2
3
4
5
6
7
8
9
10

上面代码定义了一个“类”,可以看到里面有一个constructor方法,这就是构造方法,而this关键字则代表实例对象。也就是说,ES5 的构造函数Point,对应 ES6Point类的构造方法。

# constructor构造方法

constructor方法是类的默认方法,通过new命令生成对象实例时,自动调用该方法。一个类必须有constructor方法,如果没有显式定义,一个空的constructor方法会被默认添加。

跟Java的构造方法类似。

class Point {
}

// 等同于
class Point {
  constructor() {}
}
1
2
3
4
5
6
7

# 类的实例

生成类的实例的写法,与 ES5 完全一样,也是使用new命令。前面说过,如果忘记加上new,像函数那样调用Class,将会报错。

class Point {
  // ...
}

// 报错
var point = Point(2, 3);

// 正确
var point = new Point(2, 3);
1
2
3
4
5
6
7
8
9

ES5 一样,实例的属性除非显式定义在其本身(即定义在this对象上),否则都是定义在原型上(即定义在class上)。

//定义类
class Point {
  constructor(x, y) {
    this.x = x;
    this.y = y;
  }
  toString() {
    return '(' + this.x + ', ' + this.y + ')';
  }
}
var point = new Point(2, 3);
point.toString() // (2, 3)
point.hasOwnProperty('x') // true
point.hasOwnProperty('y') // true
point.hasOwnProperty('toString') // false
point.__proto__.hasOwnProperty('toString') // true
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

上面代码中,xy都是实例对象point自身的属性(因为定义在this变量上),所以hasOwnProperty方法返回true,而toString是原型对象的属性(因为定义在Point类上),所以hasOwnProperty方法返回false。这些都与 ES5 的行为保持一致。

ES5 一样,类的所有实例共享一个原型对象。

var p1 = new Point(2,3);
var p2 = new Point(3,2);

p1.__proto__ === p2.__proto__
//true
1
2
3
4
5

上面代码中,p1p2都是Point的实例,它们的原型都是Point.prototype,所以__proto__属性是相等的。

这也意味着,可以通过实例的__proto__属性为“类”添加方法。

# 注意事项

  • 严格模式

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

  • 类不存在变量提升
new Foo(); // ReferenceError
class Foo {}
1
2
  • name 属性

由于本质上,ES6 的类只是 ES5 的构造函数的一层包装,所以函数的许多特性都被Class继承,包括name属性。

class Point {}
Point.name // "Point"
1
2

name属性总是返回紧跟在class关键字后面的类名。

  • Generator 方法

如果某个方法之前加上星号(*),就表示该方法是一个 Generator 函数。

class Foo {
  constructor(...args) {
    this.args = args;
  }
  * [Symbol.iterator]() {
    for (let arg of this.args) {
      yield arg;
    }
  }
}

for (let x of new Foo('hello', 'world')) {
  console.log(x);
}
// hello
// world
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

上面代码中,Foo类的Symbol.iterator方法前有一个星号,表示该方法是一个 Generator 函数。Symbol.iterator方法返回一个Foo类的默认遍历器,for...of循环会自动调用这个遍历器。

  • this 的指向

类的方法内部如果含有this,它默认指向类的实例。但是,必须非常小心,一旦单独使用该方法,很可能报错。

class Logger {
  printName(name = 'there') {
    this.print(`Hello ${name}`);
  }

  print(text) {
    console.log(text);
  }
}

const logger = new Logger();
const { printName } = logger;
printName(); // TypeError: Cannot read property 'print' of undefined
1
2
3
4
5
6
7
8
9
10
11
12
13

上面代码中,printName方法中的this,默认指向Logger类的实例。但是,如果将这个方法提取出来单独使用,this会指向该方法运行时所在的环境(由于 class 内部是严格模式,所以 this 实际指向的是undefined),从而导致找不到print方法而报错。

一个比较简单的解决方法是,在构造方法中绑定this,这样就不会找不到print方法了。

function selfish (target) {
  const cache = new WeakMap();
  const handler = {
    get (target, key) {
      const value = Reflect.get(target, key);
      if (typeof value !== 'function') {
        return value;
      }
      if (!cache.has(value)) {
        cache.set(value, value.bind(target));
      }
      return cache.get(value);
    }
  };
  const proxy = new Proxy(target, handler);
  return proxy;
}

const logger = selfish(new Logger());
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

# 静态方法

类相当于实例的原型,所有在类中定义的方法,都会被实例继承。如果在一个方法前,加上static关键字,就表示该方法不会被实例继承,而是直接通过类来调用,这就称为“静态方法”。

class Foo {
  static classMethod() {
    return 'hello';
  }
}

Foo.classMethod() // 'hello'

var foo = new Foo();
foo.classMethod()
// TypeError: foo.classMethod is not a function
1
2
3
4
5
6
7
8
9
10
11

特点:

  • 和java的静态方法类似。
  • 如果静态方法包含this关键字,这个this指的是类,而不是实例。
  • 父类的静态方法,可以被子类继承。
  • 静态方法也是可以从super对象上调用的。

# 实例属性的新写法

实例属性除了定义在constructor()方法里面的this上面,也可以定义在类的最顶层。

class foo {
  bar = 'hello';
  baz = 'world';

  constructor() {
    // ...
  }
}
1
2
3
4
5
6
7
8

# 静态属性

静态属性指的是 Class 本身的属性,即Class.propName,而不是定义在实例对象(this)上的属性。

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

// 新写法
class Foo {
  static prop = 1;
}
1
2
3
4
5
6
7
8
9
10

上面代码中,老写法的静态属性定义在类的外部。整个类生成以后,再生成静态属性。这样让人很容易忽略这个静态属性,也不符合相关代码应该放在一起的代码组织原则。另外,新写法是显式声明(declarative),而不是赋值处理,语义更好。

# 私有方法和私有属性

私有方法和私有属性目前只能通过约定方案,例如在私有方法前加上_下划线。

# class的继承

在ES6中也提供了像Java一样可以子类继承父类,关键字extends

class QmBody{
	constructor(x){
		this.x = x
	}
	
	getMyCar(){
	    return "本田"
	}
	
}

class QmBody2 extends QmBody{
	constructor (x){
		// 子类没有this,需要执行完父类的constructor才会有this。
		// 相当于使用super执行父类的constructor。
		// this.x = 100 不可以在super执行之前使用this
		super(x)
		this.x = 100
	}
	
	getMyCar(){
	    console.info("执行了子类的子方法")
	    // super.getMyCar() 指向父类的原型
	    return "宝马"
	}
	
	
}
let a = new QmBody2(1)
console.info(a.x) // 100
console.info(a.getMyCar())
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
  • 子类拥有父类的属性和方法
  • 子类的constructor需要使用super()执行父类的构造函数
  • 子类的子方法可以重写父类的方法
  • 子类中可以调用super.方法名调用父类的方法。
  • 子类可以继承父类的静态方法

# Promise 异步操作对象

Promise 对象用于表示一个异步操作的最终状态(完成或失败),以及其返回的值。

下面执行顺序,先执行new Promise => 其他同步代码 => 根据Promise最后执行的结果执行成功或失败的回调函数。

let pro1 = new Promise((resolve,reject) => {
	// resolve 成功函数
	// reject 失败函数
	// 当前两个函数只能执行一个
	// 执行顺序为,Promise => 当前队列中的同步代码 => then中的回调函数
	// 当Promise函数中执行遇到错误时,自动执行reject失败函数。
	console.info("Promise")
	// resolve("success") // 成功
    // throw new Exception();  // 执行失败回调
	reject("error") // 失败
})

pro1.then((res)=>{
	// 成功回调
	console.info("resolve")
	console.info(res) // success
},(e)=>{
	// 失败回调
	console.info("rejected")
	console.info(e) // error
})
// 当前队列中的同步代码
console.info("结束")
/*
打印结果:
        Promise
        结束
        resolve
        success
*/
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30

例子:异步加载图片

<!DOCTYPE html>
<html>
<head>
	<title>Promise</title>
</head>
<body>
    <div id='box'></div> 
</body>
<script>
    function loadingImage(url){
        return new Promise((resolve,reject) => {
            let img = new Image()
            img.src = url
            img.onload = function (){
                resolve(img)
            }
            img.onerror = function (e){
                reject(e)
            }
        })
    }
    var url = "https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1554718271585&di=2837ddfa490840ed62641e4c304655f6&imgtype=0&src=http%3A%2F%2Fpic1.win4000.com%2Fwallpaper%2F9%2F5450ae2fdef8a.jpg"
    // url = 'http://error'
    loadingImage(url).then((img)=>{
    	console.info('加载图片成功')
        box.appendChild(img)
    },(e)=>{
    	console.error("加载图片失败")
        console.error(e)
    })
</script>
</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32

# catch使用

catch 用作捕获错误异常。

<!DOCTYPE html>
<html>
<head>
	<title>Promise</title>
</head>
<body>
    <div id='box'></div> 
</body>
<script>
    function loadingImage(url){
        return new Promise((resolve,reject) => {
            let img = new Image()
            img.src = url
            img.onload = function (){
                resolve(img)
            }
            img.onerror = function (e){
                reject(e)
            }
        })
    }
    var url = "https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1554718271585&di=2837ddfa490840ed62641e4c304655f6&imgtype=0&src=http%3A%2F%2Fpic1.win4000.com%2Fwallpaper%2F9%2F5450ae2fdef8a.jpg"
    // url = 'http://error'
    loadingImage(url).then((img)=>{
    	console.info('加载图片成功')
        box.appendChild(img)
    }).catch((e) => {
        console.error("图片加载失败")
        console.error(e)
    })
</script>
</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32

# all的使用

  • all是数组异步回调。
  • 数组中所有的Promise都成功才会执行成功回调。
  • 只要数组中任意一个错误都会直接执行错误回调。
let p1 = new Promise((resolve,reject) => {
    resolve('js')
}

let p2 = new Promise((resolve,reject) => {
    resolve('java')
}

let p3 = new Promise((resolve,reject) => {
    resolve('css')
}

let pAll = Promise.all([p1,p2,p3])
pAll.then((res) => {
    console.info(res) // ['js','java','css']
}).catch((e) => {
    console.error(e)
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

# race的使用

Promse.race就是赛跑的意思,意思就是说,Promise.race([p1, p2, p3])里面哪个结果获得的快,就返回那个结果,不管结果本身是成功状态还是失败状态。

let p1 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve('success')
  },1000)
})

let p2 = new Promise((resolve, reject) => {
  setTimeout(() => {
    reject('failed')
  }, 500)
})

Promise.race([p1, p2]).then((result) => {
  console.log(result)
}).catch((error) => {
  console.log(error)  // 打开的是 'failed'
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

# async函数

// async 函数默认返回一个Promise对象
async function getA(){
    return 'ok'
}
getA().then((res) => {
    console.info(res)
}).catch((e) => {
    console.error(e)
})
1
2
3
4
5
6
7
8
9

# await函数

等待Promise完毕,并返回执行完毕后返回的结果。

相当于一个语法糖,不用通过then就可以拿到Promise的结果。

    let p = new Promise((resolve, reject) => {
        console.info("Promise")
        resolve("成功")
    })
    console.info("同步代码")

    async function test() {
        // await 后面是一个Promse对象,如果不是也会默认转为Promse对象
        // 等待 Promise 完毕,并返回执行完毕后返回的结果。
        let a = await p
        // 先执行完await里的异步回调,在执行下面的代码
        console.info("执行完成Promise后执行打印Promise的结果:" + a)
    }
    test()
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# esmodule 规范

# 介绍

涉及语法:import export from

  1. 如何定义模块 (一个JS就是一个模块)
  2. 如何导出模块 (export)
  3. 如何引入模块 (import)

# 简单示例

index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>index</title>
</head>
<body>
</body>
<script type="module" src="main.js"></script>
<!-- 注意:引入type类型必须指定为module -->
</html>
1
2
3
4
5
6
7
8
9
10
11
12
13

main.js

// 第三方(npm,install => node_modules) 不用./
// 自定义的 ./
// 使用解构赋值获取导出的内容
import {name,age} from "./student.js"
console.info(name)
1
2
3
4
5

student.js

export let name = "qm"
export let age = 13
// 默认导出的形式是对象形式 {name:'qm',age:13}
1
2
3

# 全部导入

html同上

mian.js

// 使用 as 给 对象定义名称,* 号表示导入全部
import * as student from "./student.js"
console.info(student.name)
1
2
3

# default的使用

mian.js

// 使用default导出后,导入只需要写个名字即可。
import student from "./student.js"
console.info(student.name)
1
2
3
let name = 'qm'
let age = 13
// 使用default一次性导出
export default {name,age}
1
2
3
4
最近更新: 2019/10/17 上午4:20:42