# ECMAScript 6
ECMAScript6
简称ES6
,2015年推出,相比ES5
在保证向下兼容的前提下,提供大量新特性。
- 块级作用域 关键字
let
, 常量const
- 对象字面量的属性赋值简写(
property
value
shorthand
)
# 1) 定义变量
# 前述
var
function
存在变量提声var
只会提前声明function
即声明又定义- 全局作用域下使用
var
和function
声明的变量会给window
增加属性
console.info(num) // undefined
console.info(getNum) // 将当前函数打印
var num = 1
function getNum(){
}
console.info(window.num) // 1
console.info('getNum' in window) // true
2
3
4
5
6
7
8
# let
- 没有变量提声
- 不可以重复声明
- 不会给
window
增加属性 - 在块级作用域下他是私有的
// num is not defined
// console.info(num) // 报错 没有变量提声
let num = 1
// let num = 2 // 报错 不可重复声明
// window 下没有 num 属性
console.info(window.num) // undefined
2
3
4
5
6
# const
- 没有变量提声
- 不可以重复声明
- 不会给
window
增加属性 const
定义变量,一旦声明必须赋值const
定义的是一个常量,不可以重复赋值
// const a; // 报错 一旦声明必须赋值
const b = 1
// b = 3 报错 不可重复赋值
2
3
# 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) // 报错
2
3
4
5
6
7
8
9
10
11
# 常见的块级作用域
if
/for
/对象....
常见问题
在这些语法中的块级作用域下的函数function
只会提声不会定义
console.info(getB) //undefined
if (true) {
// 同级就拿到了
console.info(getB)
function getB(){
}
}
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
2
3
4
5
6
7
8
解构设置默认值
// 注意:如下例所示 当x3等于undefined时,才会执行默认赋值。
let [x1,x2,x3 = 10] = [1,2]
console.info(x1,x2,x3) // 1 2 10
2
3
4
解构设置自执行函数
// 注意:如下例所示,当m3等于undefined时,才会执行自执行函数。
let [m1,m2,m3 = (function (){
console.info("进入了自执行函数")
return 22
})] = [1,2]
console.info(m1,m2,m3) // 1 2 22
2
3
4
5
6
省略解构赋值
let [,,a3] = [1,2,3]
console.info(a3) // 3
2
不定参数解构赋值
// 将后面的项放在一个数组中赋值给y3
// 扩展运算符 ...
let [y1,y2,...y3] = [1,2,3,4,5]
console.info(y1,y2,y3) // 1 2 [3,4,5]
2
3
4
5
# 对象解构赋值
常规解构赋值
// 如果变量名和属性名一样则可以直接省略
// let {name: name,age: age} = {name: '程序员',age: 22}
let {name,age} = {name: '程序员',age: 22}
console.info(name,age) // 程序员 22
2
3
4
设置解构默认值
let {name,age = 10} = {name: '学生'}
console.info(name,age)
2
解构设置自执行函数
let {name,age = (function(){
console.info("进入对象自执行函数")
return 12
})} = {name: '教师'}
console.info(name,age)
2
3
4
5
对象中其他类型赋值
let {name,age,hobby:[x1,x2,x3 = '未设置']} = {name:'青少年',age: 15,hobby:['打篮球','打游戏']}
console.info(name,age,x1,x2,x3) // 青少年 15 打篮球 打游戏 未设置
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
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
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
2
3
4
5
6
7
8
9
10
11
12
# 4) ES6中字符串的扩展
ES6
中对字符串的方法进行了一些新的扩展,包括ES6
中的模板字符串的用法。
# 字符串原型上的扩展方法
console.info(String.prototype)
# 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
2
3
# startsWith/endWith
定义和用法
startsWith()
方法用于检测字符串是否以指定的子字符串开始。- 如果是以指定的子字符串开头返回
true
,否则false
。 startsWith()
方法对大小写敏感。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
2
3
# 5) ES6中数组的扩展
ES6
中对数组的方法进行了一些新的扩展,下面是一些较为常用的方法整合。
# 数组原型的扩展方法
console.info(Array.prototype)
# 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
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
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) // 管理员 成员
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>
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);
}
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首席技术官
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);
}
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
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
2
3
示例 (2)
let arr = ['a','b','c']
// 查找数组中是否包含'c'
console.info(arr.includes('c')) // true
// 从下标1开始查找'c'
console.info(arr.includes('c',1)) // true
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>
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
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>
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
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)
}
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]
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}]
// }
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
2
3
4
5
6
7
8
上方通过new Function方式创建函数较为少用,了解即可。
# 7) 扩展运算符和箭头函数
# 扩展运算符
非数组和数组的相互转换(类数组)
有length的是类数组
let str = '123'
console.info([...str]) // (3) ["1", "2", "3"]
2
<ul>
<li></li>
<li></li>
<li></li>
<li></li>
<li></li>
</ul>
<script>
console.info([...document.getElementsByTagName("li")])
</script>
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
//
2
3
4
5
6
7
8
9
10
11
12
箭头函数的几个特点
- 箭头函数没有this指向 里面的this是上一级作用域下的this
let obj = {
fn:function () {
let f = () => {
console.info(this)
}
f()
}
}
obj.fn()
2
3
4
5
6
7
8
9
- 箭头函数没有 arguments
let fn1 = (...arg) => {
// console.info(arguments)
console.info(arg)
}
fn1(1,2,3,4) // 1 2 3 4
2
3
4
5
- 箭头函数不可以用作构造函数,因为不可以使用new执行
function FF() {}
new FF
// let F = () => {new F}
// is not a constructor
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
}
2
3
4
5
6
7
8
9
10
11
12
# Object的方法扩展
// Object() 将参数变成对象
console.info(Object(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
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)
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
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']
2
3
# Object.values
返回数组,为对象的value值数组[所有可枚举的属性]
let obj = {name:'浅梦',age:27}
console.info(Object.values(obj))
// 打印 ['浅梦',27]
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]
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函数
// 浅梦
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
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);
}
});
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
2
3
4
5
6
上面代码说明,Proxy 实际上重载(overload)了点运算符,即用自己的定义覆盖了语言的原始定义。
ES6 原生提供 Proxy 构造函数,用来生成 Proxy 实例。
var proxy = new Proxy(target, handler);
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
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"
2
3
4
5
上面代码中,handler
是一个空对象,没有任何拦截效果,访问proxy
就等同于访问target
。
一个技巧是将 Proxy
对象,设置到object.proxy
属性,从而可以在object
对象上调用。
var object = { proxy: new Proxy(target, handler) };
Proxy 实例也可以作为其他对象的原型对象。
var proxy = new Proxy({}, {
get: function(target, property) {
return 35;
}
});
let obj = Object.create(proxy);
obj.time // 35
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
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 = v
或proxy['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);
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 + ')';
}
}
2
3
4
5
6
7
8
9
10
上面代码定义了一个“类”,可以看到里面有一个constructor
方法,这就是构造方法,而this
关键字则代表实例对象。也就是说,ES5
的构造函数Point
,对应 ES6
的Point
类的构造方法。
# constructor
构造方法
constructor
方法是类的默认方法,通过new
命令生成对象实例时,自动调用该方法。一个类必须有constructor
方法,如果没有显式定义,一个空的constructor
方法会被默认添加。
跟Java的构造方法类似。
class Point {
}
// 等同于
class Point {
constructor() {}
}
2
3
4
5
6
7
# 类的实例
生成类的实例的写法,与 ES5
完全一样,也是使用new
命令。前面说过,如果忘记加上new
,像函数那样调用Class
,将会报错。
class Point {
// ...
}
// 报错
var point = Point(2, 3);
// 正确
var point = new Point(2, 3);
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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
上面代码中,x
和y
都是实例对象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
2
3
4
5
上面代码中,p1
和p2
都是Point
的实例,它们的原型都是Point.prototype
,所以__proto__
属性是相等的。
这也意味着,可以通过实例的__proto__
属性为“类”添加方法。
# 注意事项
- 严格模式
类和模块的内部,默认就是严格模式,所以不需要使用
use strict
指定运行模式。只要你的代码写在类或模块之中,就只有严格模式可用。考虑到未来所有的代码,其实都是运行在模块之中,所以ES6
实际上把整个语言升级到了严格模式。
- 类不存在变量提升
new Foo(); // ReferenceError
class Foo {}
2
- name 属性
由于本质上,ES6 的类只是 ES5 的构造函数的一层包装,所以函数的许多特性都被
Class
继承,包括name
属性。
class Point {}
Point.name // "Point"
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
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
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());
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
2
3
4
5
6
7
8
9
10
11
特点:
- 和java的静态方法类似。
- 如果静态方法包含
this
关键字,这个this
指的是类,而不是实例。 - 父类的静态方法,可以被子类继承。
- 静态方法也是可以从
super
对象上调用的。
# 实例属性的新写法
实例属性除了定义在constructor()
方法里面的this
上面,也可以定义在类的最顶层。
class foo {
bar = 'hello';
baz = 'world';
constructor() {
// ...
}
}
2
3
4
5
6
7
8
# 静态属性
静态属性指的是 Class
本身的属性,即Class.propName
,而不是定义在实例对象(this
)上的属性。
// 老写法
class Foo {
// ...
}
Foo.prop = 1;
// 新写法
class Foo {
static prop = 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())
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
*/
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>
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>
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)
})
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'
})
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)
})
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()
2
3
4
5
6
7
8
9
10
11
12
13
14
# esmodule 规范
# 介绍
涉及语法:
import
export
from
- 如何定义模块 (一个JS就是一个模块)
- 如何导出模块 (export)
- 如何引入模块 (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>
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)
2
3
4
5
student.js
export let name = "qm"
export let age = 13
// 默认导出的形式是对象形式 {name:'qm',age:13}
2
3
# 全部导入
html同上
mian.js
// 使用 as 给 对象定义名称,* 号表示导入全部
import * as student from "./student.js"
console.info(student.name)
2
3
# default的使用
mian.js
// 使用default导出后,导入只需要写个名字即可。
import student from "./student.js"
console.info(student.name)
2
3
let name = 'qm'
let age = 13
// 使用default一次性导出
export default {name,age}
2
3
4