es6语法
let和const命令
let
- let声明的变量,只在
let
命令所在的代码块内有效
{
let a = 10;
var b = 20;
}
console.log(a); //a is not defined
console.log(b); //20
2.不存在遍历提升现象
var
命令会发生变量提升
现象,即变量可以在声明之前使用,值为undefined
let声明的变量一定要在声明后使用,否则会报错
//var的情况
console.log(c);//输出undefined
var c = 30;
//let的情况
console.log(c);// 报错ReferenceError
let c = 30;
3.不允许重复声明
let在相同作用域内,重复声明同一个变量会报错
let c = 10;
let c = 30;
console.log(c); //报错
function func(arg) {
let arg; // 报错
}
4.暂时性死区
只要块级作用域内存在let
命令,它所声明的变量就“绑定”这个区域,不再受外部的影响
if (true) {
// TDZ开始
tmp = 'abc'; // ReferenceError
console.log(tmp); // ReferenceError
let tmp; // TDZ结束
console.log(tmp); // undefined
tmp = 123;
console.log(tmp); // 123
}
较隐蔽的暂时性死区
//function bar(x = y, y = 2) {
// return [x, y];
//}
//bar(); // 报错
function bar(x = 2, y = x) {
return [x, y];
}
bar(); // [2, 2]
块级作用域
为什么需要块级作用域?
原因一:内层变量可能会覆盖外层变量
function foo(a){
console.log(a);
if(1===2){
var a = 'hello ';
}
}
var a = 10;
foo(a);
原因二:用来计数的循环遍历泄露为全局变量
var arr = []
for(var i = 0; i < 10; i++){
arr[i] = function(){
return i;
}
}
console.log(arr[5]());//10
变量i
只用来控制循环,但是循环结束后,它并没有消失,用于变量提升,泄露成了全局变量
解决循环计数问题
//解决方式一:使用闭包
var arr = []
for(var i = 0; i < 10; i++){
arr[i] = (function(n){
return function(){
return n;
}
})(i)
}
//解决方式二:使用let声明i
var arr = []
for(let i = 0; i < 10; i++){
arr[i] = function () {
return i;
}
}
const
声明一个只读的常量。一旦声明,常量的值就不能改变
const a = 10;
a = 20;//报错
const b; //报错
const其本质并不是变量的值不得改动,而是变量指向的那个内存地址
所保存的数据不得改动
const foo = {};
// 为 foo 添加一个属性,可以成功
foo.prop = 123;
foo.prop // 123
// 将 foo 指向另一个对象,就会报错
foo = {}; // TypeError: "foo" is read-only
默认情况下建议使用const,当知道变量值需要被修改时再改为let即可
模板字符串
传统的 JavaScript 语言,输出模板通常是这样写的
let name = "Kimi";
let greeting = "Hello, " + name + "!";
console.log(greeting); // 输出: Hello, Kimi!
上面的这种写法相当繁琐不方便,ES6引入了模板字符串解决这个问题
let name = "Kimi";
let greeting = `Hello, ${name}!`;
console.log(greeting); // 输出: Hello, Kimi!
解构赋值
数组解构
- 将数组的单元值快速
批量赋值
给一系列变量
<script>
// 普通的数组
let arr = [1, 2, 3]
// 批量声明变量 a b c
// 同时将数组单元值 1 2 3 依次赋值给变量 a b c
let [a, b, c] = arr
console.log(a); // 1
console.log(b); // 2
console.log(c); // 3
</script>
- 数组解构细节
<script>
// const pc = ['海尔', '联想', '小米', '方正'];
// [hr, lx, mi, fz] = pc
// console.log(hr, lx, mi, fz);
// function getValue() {
// return [100, 60]
// }
// [max, min] = getValue()
// console.log(max, min);
// const pc = ['海尔', '联想', '小米', '方正']
// const [hr, lx, mi, fz] = ['海尔', '联想', '小米', '方正']
// console.log(hr)
// console.log(lx)
// console.log(mi)
// console.log(fz)
// // 请将最大值和最小值函数返回值解构 max 和min 两个变量
// function getValue() {
// return [100, 60]
// }
// const [max, min] = getValue()
// console.log(max)
// console.log(min)
// 1. 变量多, 单元值少 , undefined
// const [a, b, c, d] = [1, 2, 3]
// console.log(a) // 1
// console.log(b) // 2
// console.log(c) // 3
// console.log(d) // undefined
// 2. 变量少, 单元值多
// const [a, b] = [1, 2, 3]
// console.log(a) // 1
// console.log(b) // 2
// 3. 剩余参数 变量少, 单元值多
// const [a, b, ...c] = [1, 2, 3, 4]
// console.log(a) // 1
// console.log(b) // 2
// console.log(c) // [3, 4] 真数组
// 4. 防止 undefined 传递
// const [a = 0, b = 0] = [1, 2]
// const [a = 0, b = 0] = []
// console.log(a) // 1
// console.log(b) // 2
// 5. 按需导入赋值
// const [a, b, , d] = [1, 2, 3, 4]
// console.log(a) // 1
// console.log(b) // 2
// console.log(d) // 4
// const arr = [1, 2, [3, 4]]
// console.log(arr[0]) // 1
// console.log(arr[1]) // 2
// console.log(arr[2]) // [3,4]
// console.log(arr[2][0]) // 3
// 多维数组解构
// const arr = [1, 2, [3, 4]]
// const [a, b, c] = [1, 2, [3, 4]]
// console.log(a) // 1
// console.log(b) // 2
// console.log(c) // [3,4]
const [a, b, [c, d]] = [1, 2, [3, 4]]
console.log(a) // 1
console.log(b) // 2
console.log(c) // 3
console.log(d) // 4
</script>
注意: js 前面必须加分号情况
1.立即执行函数
2.数组解构
对象解构
- 将对象属性和方法快速批量赋值给一系列变量
<script>
// 普通对象
const user = {
name: '小明',
age: 18
};
//以前用法
//console.log(obj.name)
// 批量声明变量 name age
// 同时将数组单元值 小明 18 依次赋值给变量 name age
const {name, age} = user
//解构之后name、age就可以直接用了,不需要对象.属性来使用了
console.log(name) // 小明
console.log(age) // 18
</script>
多级对象解构:
<script>
// const pig = {
// name: '佩奇',
// family: {
// mother: '猪妈妈',
// father: '猪爸爸',
// sister: '乔治'
// },
// age: 6
// }
// // 多级对象解构
// const { name, family: { mother, father, sister } } = pig
// console.log(name)
// console.log(mother)
// console.log(father)
// console.log(sister)
const person = [
{
name: '佩奇',
family: {
mother: '猪妈妈',
father: '猪爸爸',
sister: '乔治'
},
age: 6
}
]
const [{ name, family: { mother, father, sister } }] = person
console.log(name)
console.log(mother)
console.log(father)
console.log(sister)
</script>
函数参数的解构
function add([x, y]){
return x + y;
}
add([1, 2]); // 3
使用默认值:
function addCart(n,num=0){
return n+num;
}
addCart(10);//10
addCart(10,20); //30
用途:
1.从函数返回多个值
函数只能返回一个值,若要返回多个值,只能放在数组或对象中返回
// 返回一个数组
function example() {
return [1, 2, 3];
}
let [a, b, c] = example();
// 返回一个对象
function example() {
return {
foo: 1,
bar: 2
};
}
let { foo, bar } = example();
2.函数参数的定义
解构赋值可以方便地将一组参数与变量名对应起来
// 参数是一组有次序的值
// function f([x, y, z]) {
// console.log(x,y,z);//1 2 3
// }
// f([1, 2, 3]);
// 参数是一组无次序的值
function f({x, y, z}) {
console.log(x,y,z);//1 2 3
}
f({z: 3, y: 2, x: 1});
3.提取JSON数据
解构赋值对提取 JSON 对象中的数据,尤其有用
let jsonData = {
id: 42,
status: "OK",
data: [867, 5309]
};
let { id, status, data: number } = jsonData;
//对象的解构赋值的内部机制,是先找到同名属性,然后再赋给对应的变量。真正被赋值的是后者,而不是前者
console.log(id, status, number);
// 42, "OK", [867, 5309]
4.输入模块的指定方法
加载模块时,往往需要指定输入哪些方法→解构赋值使得输入语句非常清晰。
const {ajax} = require('xxx')
ajax()
函数扩展
函数参数
默认值
ES6之前,不能直接为函数的参数指定默认值,只能采用变通的方法
function log(x,y){
y = y || 'world';
console.log(x,y);
}
log('hello');//hello world
log('hello','china') //hello china
log('hello','')//hello world
ES6 允许为函数的参数设置默认值,即直接写在参数定义的后面。
function log(x, y = 'World') {
console.log(x, y);
}
log('Hello') // Hello World
log('Hello', 'China') // Hello China
log('Hello', '') // Hello
默认的表达式可以是一个函数*
function getVal(val) {
return val + 5;
}
function add2(a, b = getVal(5)) {
return a + b;
}
console.log(add2(10));
练习
// 写法一
function m1({x = 0, y = 0} = {}) {
return [x, y];
}
// 写法二
function m2({x, y} = { x: 0, y: 0 }) {
return [x, y];
}
以上两种写法的区别:都对函数的参数设定了默认值;区别:写法一函数参数默认值为空对象,但设置了对象解构赋值的默认值;写法二函数参数默认值为一个具体属性的对象,但没有设置对象解构赋值的默认值
// 函数没有参数的情况
m1() // [0, 0]
m2() // [0, 0]
// x 和 y 都有值的情况
m1({x: 3, y: 8}) // [3, 8]
m2({x: 3, y: 8}) // [3, 8]
// x 有值,y 无值的情况
m1({x: 3}) // [3, 0]
m2({x: 3}) // [3, undefined]
// x 和 y 都无值的情况
m1({}) // [0, 0];
m2({}) // [undefined, undefined]
m1({z: 3}) // [0, 0]
m2({z: 3}) // [undefined, undefined]
剩余参数
ES6引入了rest参数(形式为…变量名),用于获取函数的多余参数,主要是为了解决arguments的问题。
按照ES5的写法,如果不确定用户传几个参数,我们肯定是使用arguments(动态参数)伪数组来接收
let book = {
title: '学前端',
name: 'zcy',
age: 18
}
function pick(obj) {
let result = {};
for (let i = 1; i < arguments.length; i++) {
result[arguments[i]] = obj[arguments[i]];
}
return result;
}
let bookData = pick(book, 'title', 'name', 'age');
console.log(bookData);
但在es6中,我们可以获取一个真正的数组。
let book = {
title: '学前端',
name: 'zcy',
age: 18
}
function pick(obj, ...args) {
console.log(args); //['title', 'name', 'age']
let result = {}; // 定义一个空对象
for (let i = 0; i < args.length; i++) {
//把对象中的属性值拿过来,给空对象(如果该对象没有这个属性,那么就添加一个)
result[args[i]] = obj[args[i]];
}
return result;
}
let bookData = pick(book, 'title', 'name', 'age');
console.log(bookData);
展开运算符
将一个数组(或对象)分割,并将数组的各个项作为分离的参数传给函数。其实就是把数组或对象拆开
应用场景:
1、比如我要获取数组的最大值,以前我们会使用apply
const arr = [123, 545, 34, 234, 5];
console.log(Math.max.apply(null, arr));
但是现在我们可以这样写
const arr = [123, 545, 34, 234, 5];
console.log(Math.max(...arr));
2、其实也可以把对象里的东西拆开
let obj1 = {x:100, y:200};
let obj2 = {
a:1,
...obj1,
b:2
}
console.log(obj2); //{a: 1, x: 100, y: 200, b: 2}
箭头函数
基本用法
在ES5中这样定义函数
let f = function(v){
return v;
}
在ES6允许使用箭头=>
定义函数
let f = v=>v;
//等同于
let f = function(v){
return v;
}
// 有一个参数
let add = value => value;
// 有两个参数
let add = (value,value2) => value + value2;
let add = (value1,value2)=>{
return value1 + value2;
}
// 无参数
let fn = () => "hello world";
let doThing = () => {
}
//如果箭头函数直接返回一个对象,必须在对象外面加上括号,否则会报错。
let getId = id => ({id: id,name: 'mjj'}) //注意
let obj = getId(1);
对象中的箭头函数
如一个Person对象,里面有eat方法:
let person = {
name: "jack",
// 以前:
eat: function (food) {
console.log(this.name + "在吃" + food);
},
// 箭头函数版:
eat2: food => console.log(person.name + "在吃" + food),// 这里拿不到this
// 简写版:
eat3(food){
console.log(this.name + "在吃" + food);
}
}
箭头函数this指向
- 箭头函数中并不存在this!!!,内部this只能通过查找作用域链来确定
- 箭头函数默认帮我们绑定外层this的值,所有在箭头函数中this值和外层this是一样的
- 箭头函数的this引用的就是最近作用域的this
<body>
<script>
// 以前this的指向: 谁调用的这个函数,this 就指向谁
// console.log(this) // window
// // 普通函数
// function fn() {
// console.log(this) // window
// }
// window.fn()
// // 对象方法里面的this
// const obj = {
// name: 'andy',
// sayHi: function () {
// console.log(this) // obj
// }
// }
// obj.sayHi()
// 2. 箭头函数的this 是上一层作用域的this 指向
// const fn = () => {
// console.log(this) // window
// }
// fn()
// 对象方法箭头函数 this
// const obj = {
// uname: 'pink老师',
// sayHi: () => {
// console.log(this) // this 指向谁? window
// }
// }
// obj.sayHi()
const obj = {
uname: 'pink老师',
sayHi: function () {
console.log(this) // obj
let i = 10
const count = () => {
console.log(this) // obj
}
count()
}
}
obj.sayHi()
</script>
</body>
let PageHandler = {
id:123,
init:function(){
document.addEventListener('click',function(event) {
this.doSomeThings(event.type);
},false);
},
doSomeThings:function(type){
console.log(`事件类型:${type},当前id:${this.id}`);
}
}
PageHandler.init();
//解决this指向问题
let PageHandler = {
id: 123,
init: function () {
// 使用bind来改变内部函数this的指向
document.addEventListener('click', function (event) {
this.doSomeThings(event.type);
}.bind(this), false);
},
doSomeThings: function (type) {
console.log(`事件类型:${type},当前id:${this.id}`);
}
}
PageHandler.init();
let PageHandler = {
id: 123,
init: function () {
// 箭头函数没有this的指向,箭头函数内部的this值只能通过查找作用域链来确定
// 如果箭头函数被一个非箭头函数所包括,那么this的值与该函数的所属对象相等,否 则是全局的window对象
document.addEventListener('click', (event) => {
console.log(this);
this.doSomeThings(event.type);
}, false);
},
doSomeThings: function (type) {
console.log(`事件类型:${type},当前id:${this.id}`);
}
}
PageHandler.init();
箭头函数参数
箭头函数中没有 arguments
,只能使用 ...
动态获取实参
<body>
<script>
// 1. 利用箭头函数来求和
const getSum = (...arr) => {
let sum = 0
for (let i = 0; i < arr.length; i++) {
sum += arr[i]
}
return sum
}
const result = getSum(2, 3, 4)
console.log(result) // 9
</script>
</body>
箭头函数的作用
使表达更加简洁
代码解读 复制代码const isEven = n => n % 2 == 0; const square = n => n * n;
简化回调函数
代码解读 复制代码// 正常函数写法 [1,2,3].map(function (x) { return x * x; }); // 箭头函数写法 [1,2,3].map(x => x * x);
注意:
1.箭头函数它不是一个对象,可认为是一个表达式或语法条
2.箭头不能使用使用new关键字来实例化对象
代码解读
复制代码let Person = ()=>{}
let p1 = new Person();// Person is not a constructor
##对象扩展
基本用法
const name = '张三';
const age = 19;
const person = {
name, //等同于name:name
age,
// 方法也可以简写
sayName() {
console.log(this.name);
}
}
person.sayName();
这种写法用于函数的返回值,将会非常方便。
function getPoint() {
const x = 1;
const y = 10;
return {x, y};
}
getPoint()
// {x:1, y:10}
对象扩展运算符
const [a, ...b] = [1, 2, 3];
a // 1
b // [2, 3]
...
)用于取出参数对象的所有可遍历属性,拷贝到当前对象之中。
代码解读
复制代码let z = { a: 3, b: 4 };
let n = { ...z };
n // { a: 3, b: 4 }
数组扩展
扩展运算符-替代apply()方法
由于扩展运算符可以展开数组,所以不再需要apply()
方法将数组转为函数的参数了
// ES5 的写法
function f(x, y, z) {
// ...
}
var args = [0, 1, 2];
f.apply(null, args);
// ES6 的写法
function f(x, y, z) {
// ...
}
let args = [0, 1, 2];
f(...args);
例子1:应用Math.max()
方法,简化求出一个数组最大元素
// ES5 的写法
Math.max.apply(null, [14, 3, 77])
// ES6 的写法
Math.max(...[14, 3, 77])
// 等同于
Math.max(14, 3, 77);
例子2
// ES5 的写法
var arr1 = [0, 1, 2];
var arr2 = [3, 4, 5];
Array.prototype.push.apply(arr1, arr2);
// ES6 的写法
let arr1 = [0, 1, 2];
let arr2 = [3, 4, 5];
arr1.push(...arr2);
from()
将伪数组转换为真数组
1.将arguments转换为一个真数组
function add() {
let arr = Array.from(arguments);
console.log(arr); //[1, 2, 3]
}
add(1, 2, 3);
2.比如ul里面的好多li的数组集合,转换为真正的数组
<body>
<ul>
<li>1</li>
<li>2</li>
<li>3</li>
</ul>
<script>
// Array.from(lis) 把伪数组转换为真数组
const lis = document.querySelectorAll('ul li')
// console.log(lis)
// lis.pop() 报错
const liss = Array.from(lis)
liss.pop()
console.log(liss)
</script>
</body>
也可直接用展开运算符
console.log([...lis]); // [li, li, li, li]
3.from()还可以有第二个参数(可选),是一个映射函数→允许你在将每个元素添加到新数组时对其进行处理。
语法:
Array.from(arrayLike[, mapFn[, thisArg]])
//arrayLike:一个类似数组的对象
//mapFn(可选):一个映射函数,用于在将元素添加到新数组时对其进行处理
//thisArg(可选):执行映射函数时使用的 this 值
示例:
// 使用字符串创建数组
let str='hello'
// 字符串是一个可迭代对象,因为它具有length属性,且可以通过索引访问每个字符
// Array.from()遍历字符串的每个字符→从索引0-索引4→每个字符("H", "e", "l", "l", "o")被依次添加到新数组
let arr=Array.from(str)
console.log(arr);//输出:['h', 'e', 'l', 'l', 'o']
// 使用映射函数对每个元素进行处理
//这个映射函数被调用时,当前元素作为第一个参数,当前元素的索引作为第二个参数
let squares = Array.from({length: 5}, (v, i) => i + 1);
console.log(squares); // 输出: [1, 2, 3, 4, 5]
// 使用映射函数对数组的每个元素进行平方处理
let arr = [1, 2, 3, 4, 5];
let squared = Array.from(arr, x => x * x);
console.log(squared); // 输出: [1, 4, 9, 16, 25]
of()
将任意数据类型转换为数组
console.log(Array.of('2', [2, 3], { 'a': 1 }, undefined));
//['2', Array(2), {…}, undefined]
find()
和findiIndex()
1.find()是找出数组中符合条件的第一个数组成员
const array = [1, 2, 3, 4, 5];
const found = array.find(element => element > 3);
console.log(found); // 输出: 4
2.findexIndex()是找出数组中符合条件的第一个数组成员的索引
const array = [1, 2, 3, 4, 5];
const index = array.findIndex(element => element > 3);
console.log(index); // 输出: 3
数组中的keys()
和values()
和entries()
遍历
它们都返回一个遍历器对象,可以用for…of循环进行遍历
keys()
用于获取数组索引的遍历values()
用于获取数组值的遍历entries()
用于获取数组键/值对的遍历
const array = ['a', 'b', 'c'];
// 遍历键
for (let key of array.keys()) {
console.log(key); // 输出: 0 然后是 1 然后是 2
}
// 遍历值
for (let value of array.values()) {
console.log(value); // 输出: 'a' 然后是 'b' 然后是 'c'
}
// 遍历键/值对
for (let entry of array.entries()) {
console.log(entry); // 输出: [0, 'a'] 然后是 [1, 'b'] 然后是 [2, 'c']
}
includes()
判断某个元素是否在数组中
用于判断一个数组是否包含一个特定的值,根据情况,它能够返回 true
或 false
const array = [1, 2, 3, 4, 5];
// 检查数组是否包含值 3
console.log(array.includes(3)); // 输出: true
// 检查数组是否包含值 6
console.log(array.includes(6)); // 输出: false
// 使用 fromIndex 选项
console.log(array.includes(2, 3)); // 输出: false,因为在索引 3 之后找不到值 2
Symbol类型
- 唯一性:每个通过
Symbol()
创建的符号都是唯一的,即便描述(description)相同,创建的符号也不同。 - 不可变:
Symbol
值是不可改变的。 - 不可枚举:使用
Symbol
定义的属性不会出现在对象的for...in
、Object.keys()
、或JSON.stringify()
等操作中。
<script>
// 原始数据类型Symbol,它表示独一无二的值
// 最大用途:用来定义对象的私有变量
const name=Symbol('name')
const name2=Symbol('name')
console.log(name===name2);//false
let s1=Symbol('s1')
console.log(s1);//Symbol(s1)
let obj={
[s1]:'学前端'
}
// obj[s1]='学前端'
// console.log(obj);//{Symbol(s1): '学前端'}
// console.log(obj[s1]);//学前端
// 获取Symbol声明的属性
let m=Object.getOwnPropertySymbols(obj)
console.log(m[0]);//Symbol(s1)
let s=Reflect.ownKeys(obj)
console.log(s[0]);
</script>
Set和Map数据结构
set
<script>
// 集合:表示无重复值的有序列表
let set =new Set()
console.log(set);//Set(0) {size: 0}
// 添加元素
set.add(2)
set.add(4)
set.add('4')
set.add(['hello','hi','fine'])
// 删除元素
set.delete(2)
// 校验某个值是否在set中
console.log( set.has(4));
console.log( set.size);
// 将set转换成数组
let set2=new Set([1,2,3,5,6,3])
console.log(set2);//Set(5) {1, 2, 3, 5, 6}
// 扩展运算符
let arr=[...set2]
console.log(arr);//[1, 2, 3, 5, 6]
// 1.set中对象的引用无法被释放
// let set3=new Set(),obj={}
// set3.add(obj)
// // 释放当前的资源
// obj=null
// console.log(set3);
// 2.通过弱引用可被释放
let set4=new WeakSet(),obj={}
set4.add(obj)
// 释放当前资源
obj=null
console.log(set4);
</script>
map
<script>
// Map类型是键值对的有序列表,键和值是任意类型
// let map=new Map()
// map.set('name','张三')
// map.set('age',20)
// console.log(map.get('name'));
// console.log(map.delete('name'));
// console.log(map.clear());
// map.set(['a',[1,2,3],'hello'])
// console.log(map);
</script>
proxy和Reflect
proxy
监听对象属性
在之前,若希望监听一个对象的相关操作,可通过Object.defineProperty
的存储属性来进行监听,它必须去深度遍历
对象里的每一个属性
<script>
const obj={
name:'why',
age:18,
height:1.88
}
// 需求:监听对象属性的所有操作
// 监听属性的操作
// 1.只针对一个属性
// let _name=obj.name
// Object.defineProperty(obj,'name',{
// set: function(newValue){
// console.log("监听:给name设置了新值:",newValue);
// _name=newValue
// },
// get: function(){
// console.log("监听:获取name的值");
// return _name
// }
// })
// 2.监听所有的属性:遍历所有的属性,对每一个属性使用defineProperty
const keys=Object.keys(obj)
for(const key of keys){
let value=obj[key]
Object.defineProperty(obj,key,{
set: function(newValue){
console.log(`监听:给${key}设置了新值`,newValue);
value=newValue
},
get: function(){
console.log(`监听:获取${key}的值`);
return value
}
})
}
console.log(obj.name);
console.log(obj.age);
obj.name='zcy'
obj.age=21
</script>
缺点:
1.其设计初衷既不是为了去监听一个完整的对象
2.目前我们只能用它监听
属性的设置和获取
,不能监听增加和删除
在ES6中,新增了一个
Proxy类
,就是为了我们监听另外一个对象而生的先创建一个
代理对象
,之后对该对象的所有操作
,都通过代理对象完成
,代理对象可监听我们想要原对象进行哪些操作
<script>
const obj={
name:'why',
age:18,
height:1.88
}
// 1.创建一个代理对象
const proxy = new Proxy(obj,{
set:function(target,key,newValue){
console.log(`监听:监听${key}的设置值:`,newValue);
target[key]=newValue
},
get:function(target,key){
console.log(`监听:监听${key}的获取`);
return target[key]
}
})
// 对obj的所有操作,应该去操作objProxy
console.log(proxy.name);//why
proxy.name='zcy'
console.log(proxy.name);//zcy
// 新增
proxy.address='广州'
</script>
其它捕获器的监听
代码实例:
<script>
const obj={
name:'why',
age:18,
height:1.88
}
// 1.创建一个代理对象
const proxy = new Proxy(obj,{
set:function(target,key,newValue){
console.log(`监听:监听${key}的设置值:`,newValue);
target[key]=newValue
},
get:function(target,key){
console.log(`监听:监听${key}的获取`);
return target[key]
},
deleteProperty:function(target,key){
const deleted = delete target[key]
console.log(`监听:监听删除${key}属性,删除操作结果:${deleted}`);
return delete target[key]
},
has:function(target,key){
console.log(`监听:监听in判断${key}属性`);
return key in target
}
})
// delete
delete proxy.name;
// has
console.log("age"in proxy);
</script>
Reflect
提供许多操作JavaScript对象的方法
,有点像Object中操作对象的方法
;
已经有Object,那为啥还需要Reflect嘞?
1.由于早期ECMA规范并未考虑到
对象本身的操作如何设计更加规范
,故将这些API放在Object身上
2.Object作为一个
构造函数
,这些操作实际上放到它身上并不太合适
3.ES6中
新增Reflect
,让这些操作集中到Reflext身上
reflect和Object的区别之一
<script>
"use strict"
const obj={
name:'acy',
age:18
}
Object.defineProperty(obj,'name',{
configurable:false
})
// 1.用以前的方式进行操作
// delete obj.name
// if(obj.name){
// console.log('name删除成功');
// }else{
// console.log('name没有删除成功');
// }
// 2.Reflect
if(Reflect.deleteProperty(obj,'name')){
console.log('name删除成功');
}else{
console.log('name没有删除成功');
}
</script>
Reflect和Proxy共同完成代理
<script>
obj={
name:'zcy',
age:19
}
const proxy=new Proxy(obj,{
set:function(target,key,newValue,receiver){
// target[key]=newValue
// 代理对象的目的:不再直接操作原对象
// 1.好处一:代理对象的目的:不再直接操作原对象
// 2.好处二:Reflect.set有返回布尔值,可以判断本次操作是否成功
console.log(receiver);
const isSuccess=Reflect.set(target,key,newValue)
if(!isSuccess){
throw new Error(`set ${key} failure`)
}
},
get:function(target,key,receiver){
}
})
console.log(obj);
</script>
receiver的作用:
<script>
obj={
_name:'zcy',
set name(newValue){
console.log("this",this);
this._name=newValue
},
get name(){
return this._name
}
}
const proxy=new Proxy(obj,{
set:function(target,key,newValue,receiver){
// target[key]=newValue
// 代理对象的目的:不再直接操作原对象
// 1.好处一:代理对象的目的:不再直接操作原对象
// 2.好处二:Reflect.set有返回布尔值,可以判断本次操作是否成功
// 3.好处三:receiver就是我们外面的proxy对象;Reflect.set、get的最后一个参数,可以决定对象访问器setter/getter的this指向
// console.log(receiver);
console.log('proxy设置方法被调用');
const isSuccess=Reflect.set(target,key,newValue,receiver)
if(!isSuccess){
throw new Error(`set ${key} failure`)
}
},
get:function(target,key,receiver){
return Reflect.get(target,key,receiver)
}
})
// 操作代理对象
proxy.name='sss'
</script>
迭代器Iterator
- 迭代器(Iterator)是一种允许你逐个访问一个数据集合中元素的接口。迭代器模式是一种设计模式,主要用于遍历一个聚合对象,如数组,或者某些类似数组的对象(例如
NodeList
) - 一个数据结构只要具有
Symbol.iterator
属性,就说明有interator接口,就可以被for...of
遍历
<script>
// Iterator
// 是一种新的遍历机制,两个核心
// 1.迭代器是一个接口,能快捷的访问数据,通过Symbol.iterator来创建迭代器 通过迭代器的next()获取迭代之后的结果
// 2.迭代器是用于遍历数据结构的指针(数据库的游标)
// 使用迭代
const items=['one','two','three']
// 1.创建新的迭代器
const ite=items[Symbol.iterator]()
console.log(ite.next());//{value: 'one', done: false} done→如果为false表示遍历继续,如果为true,表示遍历完成
console.log(ite.next());//{value: 'two', done: false}
console.log(ite.next());//{value: 'three', done: false}
</script>
生成器Generator
Generator函数的写法:function* fn() {yield},yield相当于return
Generator 函数的调用方法与普通函数一样,也是在函数名后面加上一对圆括号。不同的是,调用 Generator 函数后,该函数并不执行,返回的也不是函数运行结果,而是一个指向内部状态的指针对象,也就是遍历器对象
yield
当每次调用next方法,内部指针就从函数头部或上次停下来的地方开始执行,直到遇到下一个yield表达式(或return语句)为止
即Generator 函数是分段执行的,yield表达式是暂停执行的标记,而next方法可以恢复执行
遍历器对象next方法的运行逻辑:
(1)遇到yield表达式,就暂停执行后面的操作,并将紧跟在yield后面的那个表达式的值,作为返回的对象的value属性值
(2)下一次调用next方法时,再继续往下执行,直到遇到下一个yield表达式
(3)如果没有再遇到新的yield表达式,就一直运行到函数结束,直到return语句为止,并将return语句后面的表达式的值,作为返回的对象的value属性值
(4)如果该函数没有return语句,则返回的对象的value属性值为undefined
function* fn() {
console.log('start');
yield 2;
yield 'hello world';
return 'end'
}
let gen = fn();
console.log(gen.next()); //{value: 2, done: false}
console.log(gen.next()); //{value: 'hello world', done: false}
console.log(gen.next()); //{value: 'end', done: true}
console.log(gen.next()); //{value: undefined, done: true}
Generator 函数可以不用yield表达式,此时会变成一个单纯的暂缓执行函数:
function* f() {
console.log('执行了!')
}
var generator = f();
setTimeout(function () {
generator.next()
}, 2000);
上面代码中,函数f如果是普通函数,在为变量generator赋值时就会执行。
但是,函数f是一个 Generator 函数,就变成只有调用next方法时,函数f才会执行
。
Generator和Iterator接口的关系
任何一个对象,只要有Symbol.iterator
方法(iterator接口),就可以调用该方法生成一个遍历器(迭代器)对象;
若没有,可给当前对象赋值一个interator接口,再进行遍历
// 使用场景:为不具备Interator接口的对象提供了遍历操作
function* ObjectEntries(obj){
// 获取对象所有属性名保存到数组[name,age]
const propKeys=Object.keys(obj)
for (const propKey of propKeys){
yield[propKey,obj[propKey]]
}
}
const obj={
name:'acy',
age:20
}
// 给当前对象赋值一个interator接口
obj[Symbol.iterator]=ObjectEntries
console.log(obj);
for(let [key,value] of ObjectEntries(obj)){
console.log(`${key},${value}`);
}
next方法的参数
yield表达式本身没有返回值,或者说总是返回undefined
。next方法可以带一个参数,该参数就会被当作上一个yield表达式的返回值
。
如果不传参, 第二次不带参数,那么y = 2 * undefined = NaN,z = undefined / 3 = NaN
,第三次不带参数,那么z = undefined
, 返回5 + NaN + undefined = NaN
如果传参,那么第二次参数传给上一个yield
也就是y = 2 * 12 = 24, value: 24 /3 = 8
,第三次参数传给上一个yield
也就是z = 13, value: 5 + 24 + 13 = 42
function* foo(x) {
var y = 2 * (yield (x + 1));
var z = yield (y / 3);
return (x + y + z);
}
var a = foo(5);
a.next() // Object{value:6, done:false}
a.next() // Object{value:NaN, done:false} 第二次不带参数,那么y=2*undefined=NaN,z=undefined/3 =NaN
a.next() // Object{value:NaN, done:true} 第三次不带参数,那么z=undefined, 返回5+NaN+undefined=NaN
var b = foo(5);
b.next() // { value:6, done:false }
b.next(12) // { value:8, done:false } //第二次参数传给上一个yield也就是y=2*12=24,value:24/3=8
b.next(13) // { value:42, done:true } //第三次参数传给上一个yield也就是z=13,value:5+24+13=42
示例代码:
<script>
// 1.generator函数 可以通过yield关键字,将函数挂起,为改变执行流程提供了可能,也为异步变成提供方案
// 2.它与普通函数的区别
// 2.1function后面 函数名之前有个*
// 2.2只能在函数内部使用yield表达式,让函数挂起
function* func(a){
console.log('one');
yield 2;
console.log('two');
yield 3;
console.log('end');
}
// console.log(func());
// 返回一个遍历器对象 可以调用next方法
// let fn=func()
// console.log(fn.next());
// console.log(fn.next());
// console.log(fn.next());
/*
总结:generator函数是分段执行的
yield语句是暂停执行
next()恢复执行
*/
function* add(){
console.log('start');
// x 不是yield '2' 的返回值,它是next()调用 恢复当前yield()执行传入的实参
let x = yield '2'
console.log('one:'+x);
let y = yield '3'
console.log('two:'+y);
return x+y
}
const fn = add()
console.log(fn.next());//{value: '2', done: false}
console.log(fn.next(20));//{value: '3', done: false}
console.log(fn.next(30));//{value: 50, done: true}
</script>
Promise对象
基础认识
- 表示(管理)一个异步操作最终完成(或失败)及其结果值
- 通过学习Promise,可以知道成功和失败状态,可以关联对应处理函数,了解 axios 内部运作的原理
Promise三种状态
每个 Promise 对象必定处于以下三种状态之一:
- 待定(pending):初始状态,既没有被兑现,也没有被拒绝
- 已兑现(fulfilled):操作成功完成
- 已拒绝(rejected):操作失败
<body>
<script>
/**
* 目标:认识Promise状态
*/
// 1. 创建Promise对象(pending-待定状态)
const p = new Promise((resolve, reject) => {
// Promise对象创建时,这里的代码都会执行了
// 2. 执行异步代码
setTimeout(() => {
// resolve() => 'fulfilled状态-已兑现' => then()
resolve('模拟AJAX请求-成功结果')
// reject() => 'rejected状态-已拒绝' => catch()
reject(new Error('模拟AJAX请求-失败结果'))
}, 2000)
})
console.log(p)
// 3. 获取结果
p.then(result => {
console.log(result)
}).catch(error => {
console.log(error)
})
</script>
</body>
resolve
和reject
1.
resolve
: 将Promise对象的状态从Pending(进行中)
变为Fulfilled(已成功)
2.
reject
: 将Promise对象的状态从Pending(进行中)
变为Rejected(已失败)
3.
resolve
和reject
都可以传入任意类型的值作为实参,表示Promise
对象成功(Fulfilled)
和失败(Rejected)
的值
Promise封装XHR对象
<body>
<p class="my-p"></p>
<script>
/**
* 目标:使用Promise管理XHR请求省份列表
* 1. 创建Promise对象
* 2. 执行XHR异步代码,获取省份列表
* 3. 关联成功或失败函数,做后续处理
*/
// 1. 创建Promise对象
const p = new Promise((resolve, reject) => {
// 2. 执行XHR异步代码,获取省份列表
const xhr = new XMLHttpRequest()
xhr.open('GET', 'http://hmajax.itheima.net/api/province')
xhr.addEventListener('loadend', () => {
// xhr如何判断响应成功还是失败的?
// 2xx开头的都是成功响应状态码
if (xhr.status >= 200 && xhr.status < 300) {
resolve(JSON.parse(xhr.response))
} else {
reject(new Error(xhr.response))
}
})
xhr.send()
})
// 3. 关联成功或失败函数,做后续处理
p.then(result => {
console.log(result)
document.querySelector('.my-p').innerHTML = result.list.join('<br>')
}).catch(error => {
// 错误对象要用console.dir详细打印!!!
console.dir(error)
// 服务器返回错误提示消息,插入到p标签显示
document.querySelector('.my-p').innerHTML = error.message
})
</script>
</body>
Promise链式调用
基本用法
依靠 then() 方法会返回一个新生成的 Promise 对象特性,继续串联下一环任务,直到结束
可解决回调函数嵌套问题
<body>
<script>
/**
* 目标:掌握Promise的链式调用
* 需求:把省市的嵌套结构,改成链式调用的线性结构
*/
// 1. 创建Promise对象-模拟请求省份名字
const p = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('北京市')
}, 2000)
})
// 2. 获取省份名字
const p2 = p.then(result => {
console.log(result)
// 3. 创建Promise对象-模拟请求城市名字
// return Promise对象最终状态和结果,影响到新的Promise对象
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(result + '--- 北京')
}, 2000)
})
})
// 4. 获取城市名字
p2.then(result => {
console.log(result)
})
// then()原地的结果是一个新的Promise对象
console.log(p2 === p)
</script>
</body>
解决回调函数地狱问题
每个 Promise 对象中管理一个异步任务,用 then 返回 Promise 对象,串联起来
<body>
<form>
<span>省份:</span>
<select>
<option class="province"></option>
</select>
<span>城市:</span>
<select>
<option class="city"></option>
</select>
<span>地区:</span>
<select>
<option class="area"></option>
</select>
</form>
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
<script>
/**
* 目标:把回调函数嵌套代码,改成Promise链式调用结构
* 需求:获取默认第一个省,第一个市,第一个地区并展示在下拉菜单中
*/
let pname = ''
// 1. 得到-获取省份Promise对象
axios({url: 'http://hmajax.itheima.net/api/province'}).then(result => {
pname = result.data.list[0]
document.querySelector('.province').innerHTML = pname
// 2. 得到-获取城市Promise对象
return axios({url: 'http://hmajax.itheima.net/api/city', params: { pname }})
}).then(result => {
const cname = result.data.list[0]
document.querySelector('.city').innerHTML = cname
// 3. 得到-获取地区Promise对象
return axios({url: 'http://hmajax.itheima.net/api/area', params: { pname, cname }})
}).then(result => {
console.log(result)
const areaName = result.data.list[0]
document.querySelector('.area').innerHTML = areaName
})
</script>
</body>
Promise.all 静态方法
合并多个 Promise 对象,等待所有同时成功完成(或某一个失败),做后续逻辑
<script>
/**
* 目标:掌握Promise的all方法作用,和使用场景
* 业务:当我需要同一时间显示多个请求的结果时,就要把多请求合并
* 例如:默认显示"北京", "上海", "广州", "深圳"的天气在首页查看
* code:
* 北京-110100
* 上海-310100
* 广州-440100
* 深圳-440300
*/
// 1. 请求城市天气,得到Promise对象
const bjPromise = axios({ url: 'http://hmajax.itheima.net/api/weather', params: { city: '110100' } })
const shPromise = axios({ url: 'http://hmajax.itheima.net/api/weather', params: { city: '310100' } })
const gzPromise = axios({ url: 'http://hmajax.itheima.net/api/weather', params: { city: '440100' } })
const szPromise = axios({ url: 'http://hmajax.itheima.net/api/weather', params: { city: '440300' } })
// 2. 使用Promise.all,合并多个Promise对象
const p = Promise.all([bjPromise, shPromise, gzPromise, szPromise])
p.then(result => {
// 注意:结果数组顺序和合并时顺序是一致
console.log(result)
const htmlStr = result.map(item => {
return `<li>${item.data.data.area} --- ${item.data.data.weather}</li>`
}).join('')
document.querySelector('.my-ul').innerHTML = htmlStr
}).catch(error => {
console.dir(error)
})
</script>
async 函数
基本用法
- 在 async 函数内,使用 await 关键字取代 then 函数,等待获取 Promise 对象成功状态的结果值
- await替代 then 方法来提取 Promise 对象成功状态的结果
- async/await使得异步代码看起来像同步代码,再也没有回调函数。但是改变不了JS单线程、异步的本质。(异步代码同步化)
<body>
<form>
<span>省份:</span>
<select>
<option class="province"></option>
</select>
<span>城市:</span>
<select>
<option class="city"></option>
</select>
<span>地区:</span>
<select>
<option class="area"></option>
</select>
</form>
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
<script>
/**
* 目标:掌握async和await语法,解决回调函数地狱
* 概念:在async函数内,使用await关键字,获取Promise对象"成功状态"结果值
* 注意:await必须用在async修饰的函数内(await会阻止"异步函数内"代码继续执行,原地等待结果)
*/
// 1. 定义async修饰函数
async function getData() {
// 2. await等待Promise对象成功的结果
const pObj = await axios({url: 'http://hmajax.itheima.net/api/province'})
const pname = pObj.data.list[0]
const cObj = await axios({url: 'http://hmajax.itheima.net/api/city', params: { pname }})
const cname = cObj.data.list[0]
const aObj = await axios({url: 'http://hmajax.itheima.net/api/area', params: { pname, cname }})
const areaName = aObj.data.list[0]
document.querySelector('.province').innerHTML = pname
document.querySelector('.city').innerHTML = cname
document.querySelector('.area').innerHTML = areaName
}
getData()
</script>
</body>
捕获错误
用 try catch 捕获同步流程的错误
<script>
/**
* 目标:async和await_错误捕获
*/
async function getData() {
// 1. try包裹可能产生错误的代码
try {
const pObj = await axios({ url: 'http://hmajax.itheima.net/api/province' })
const pname = pObj.data.list[0]
const cObj = await axios({ url: 'http://hmajax.itheima.net/api/city', params: { pname } })
const cname = cObj.data.list[0]
const aObj = await axios({ url: 'http://hmajax.itheima.net/api/area', params: { pname, cname } })
const areaName = aObj.data.list[0]
document.querySelector('.province').innerHTML = pname
document.querySelector('.city').innerHTML = cname
document.querySelector('.area').innerHTML = areaName
} catch (error) {
// 2. 接着调用catch块,接收错误信息
// 如果try里某行代码报错后,try中剩余的代码不会执行了
console.dir(error)
}
}
getData()
</script>
Class
基本用法
ES6中的class其实就类似于ES5中的构造函数
比如下面两个写法是等价的:
<script>
// es5构造函数
// function Person(name,age){
// this.name=name
// this.age=age
// }
// // 给它一些共用的方法
// Person.prototype.satName=function(){
// return this.name
// }
// let p1=new Person('zcy',18)
// console.log(p1);
// es6
class Person{
// 实例化的时候会被立即调用
constructor(name,age){
this.name=name
this.age=age
}
// 给它赋值方法
// sayName(){
// return this.name
// }
// sayAge(){
// return this.age
// }
}
// 通过Object.assign()一次性向类中添加多个方法
Object.assign(Person.prototype, {
sayName(){
return this.name
},
sayAge(){
return this.age
}
})
let p1=new Person('zcy',18)
console.log(p1);
</script>
类的继承
1.Class 可以通过extends
关键字实现继承,让子类继承父类的属性和方法
2super
关键字调用父类中的构造函数,改变父类中的this指向,让父类中的this指向子类
3.子类在继承父类的方法后,可以重写父类的方法
<script>
// 使用关键字
class Animal{
constructor(name,age){
this.name=name
this.age=age
}
sayName(){
return this.name
}
sayAge(){
return this.age
}
}
// 创建一个Dog
class Dog extends Animal{
constructor(name,age,color){
super(name,age)
// Animal.call(this,name,age)
this.color=color
}
sayColor(){
return `${this.name}是${this.age}岁了,它的颜色是${this.color}`
}
// 重写父类的方法
sayName(){
// super相当于Animal.prototype
return this.name+super.sayAge()+this.color
}
}
let d=new Dog('小白',10,'red')
console.log(d.sayAge());
console.log(d.sayName());
</script>
Module 模块化
es6模块主要功能由两个命令构成:
export
和import
export用于规定模块的对外接口
import用于输入其它模块提供的功能
export命令
一个模块就是一个独立文件。如果想从在不读取模块内部的某个变量,就必须使用export关键字输出该变量
//module/index.js
export const name = 'zhangsan ';
export const age = 18;
export const color = 'red ';
export const sayName = function() {
console.log(fristName);
}
//也可以这样
const name = 'zhangsan ';
const age = 18;
const color = 'red ';
const sayName = function() {
console.log(fristName);
}
export {name,age,color,sayName}
import命令
使用export
命令定义了模块的对外接口以后,其他 JS 文件就可以通过import
命令加载这个模块
//main.js
import {name,age,color,sayName,fn} from './modules/index.js';
如果想为输入的变量重新取一个名字,import
命令要使用as
关键字,将输入的变量重命名
import * as obj from './modules/index.js';
console.log(obj);
export default 命令
使用export default
命令为模块指定默认输出
//export-default.js
export default function(){
console.log('foo');
}
//或者写成
function foo() {
console.log('foo');
}
export default foo;
在其它模块加载该模块时,import
命令可以为该匿名函数指定任意名字
//import-default.js
import customName from './export-default.js'
customNmae();//foo
如果想在一条import语句中,同事输入默认方法和其他接口,可以写成下面这样
import customName,{add} from 'export-default.js'
//export-default.js
export default function(){
console.log('foo');
}
export function add(){
console.log('add')
}
export default
也可以用来输出类
// MyClass.js
export default class Person{ ... }
// main.js
import Person from 'MyClass';
let o = new Person();
Decorator
介绍
Decorator(装饰器)是一种设计模式,允许用户在不改变原类和使用继承的情况下,动态地扩展类属性和类方法
这里定义一个士兵,这时候他什么装备都没有
class soldier{
}
定义一个得到 AK 装备的函数,即装饰器
function strong(target){
target.AK = true
}
使用该装饰器对士兵进行增强
@strong
class soldier{
}
这时候士兵就有武器了
soldier.AK // true
通过上述代码可得知Decorator的优点:
1.代码可读性变强,装饰器命名相当于一个注释
2.在不改变原有代码情况下,对原来功能进行扩展
用法
类的装饰
当对类本身进行装饰的时候,能够接受一个参数,即类本身
将装饰器行为进行分解,大家能够有个更深入的了解
@decorator
class A {}
// 等同于
class A {}
A = decorator(A) || A;
下面@testable
就是一个装饰器,target
就是传入的类,即MyTestableClass
,实现了为类添加静态属性
@testable
class MyTestableClass {
// ...
}
function testable(target) {
target.isTestable = true;
}
MyTestableClass.isTestable // true
如果想要传递参数,可以在装饰器外层再封装一层函数
function testable(isTestable) {
return function(target) {
target.isTestable = isTestable;
}
}
@testable(true)
class MyTestableClass {}
MyTestableClass.isTestable // true
@testable(false)
class MyClass {}
MyClass.isTestable // false
类属性的装饰
当对类属性进行装饰的时候,能够接受三个参数:
- 类的原型对象
- 需要装饰的属性名
- 装饰属性名的描述对象
首先定义一个readonly
装饰器
function readonly(target, name, descriptor){
descriptor.writable = false; // 将可写属性设为false
return descriptor;
}
使用readonly
装饰类的name
方法
class Person {
@readonly
name() { return `${this.first} ${this.last}` }
}
相当于以下调用
readonly(Person.prototype, 'name', descriptor);
如果一个方法有多个装饰器,就像洋葱一样,先从外到内进入,再由内到外执行
function dec(id){
console.log('evaluated', id);
return (target, property, descriptor) =>console.log('executed', id);
}
class Example {
@dec(1)
@dec(2)
method(){}
}
// evaluated 1
// evaluated 2
// executed 2
// executed 1
外层装饰器@dec(1)
先进入,但是内层装饰器@dec(2)
先执行
注意:
装饰器不能用于修饰函数,因为函数存在变量声明情况
var counter = 0;
var add = function () {
counter++;
};
@add
function foo() {
}
编译阶段,变成下面
var counter;
var add;
@add
function foo() {
}
counter = 0;
add = function () {
counter++;
};
意图是执行后counter
等于 1,但是实际上结果是counter
等于 0
使用场景
基于Decorator
强大的作用,我们能够完成各种场景的需求,下面简单列举几种:
使用react-redux
的时候,如果写成下面这种形式,既不雅观也很麻烦
class MyReactComponent extends React.Component {}
export default connect(mapStateToProps, mapDispatchToProps)(MyReactComponent);
通过装饰器就变得简洁多了
@connect(mapStateToProps, mapDispatchToProps)
export default class MyReactComponent extends React.Component {}
将mixins
,也可以写成装饰器,让使用更为简洁了
function mixins(...list) {
return function (target) {
Object.assign(target.prototype, ...list);
};
}
// 使用
const Foo = {
foo() { console.log('foo') }
};
@mixins(Foo)
class MyClass {}
let obj = new MyClass();
obj.foo() // "foo"
下面再讲讲core-decorators.js
几个常见的装饰器
1.@antobind
autobind
装饰器使得方法中的this
对象,绑定原始对象
import { autobind } from 'core-decorators';
class Person {
@autobind
getPerson() {
return this;
}
}
let person = new Person();
let getPerson = person.getPerson;
getPerson() === person;
// true
2.@readonly
readonly
装饰器使得属性或方法不可写
import { readonly } from 'core-decorators';
class Meal {
@readonly
entree = 'steak';
}
var dinner = new Meal();
dinner.entree = 'salmon';
// Cannot assign to read only property 'entree' of [object Object]
3.@deprecate
deprecate
或deprecated
装饰器在控制台显示一条警告,表示该方法将废除
import { deprecate } from 'core-decorators';
class Person {
@deprecate
facepalm() {}
@deprecate('功能废除了')
facepalmHard() {}
}
let person = new Person();
person.facepalm();
// DEPRECATION Person#facepalm: This function will be removed in future versions.
person.facepalmHard();
// DEPRECATION Person#facepalmHard: 功能废除了
再由内到外执行
function dec(id){
console.log('evaluated', id);
return (target, property, descriptor) =>console.log('executed', id);
}
class Example {
@dec(1)
@dec(2)
method(){}
}
// evaluated 1
// evaluated 2
// executed 2
// executed 1
外层装饰器@dec(1)
先进入,但是内层装饰器@dec(2)
先执行
注意:
装饰器不能用于修饰函数,因为函数存在变量声明情况
var counter = 0;
var add = function () {
counter++;
};
@add
function foo() {
}
编译阶段,变成下面
var counter;
var add;
@add
function foo() {
}
counter = 0;
add = function () {
counter++;
};
意图是执行后counter
等于 1,但是实际上结果是counter
等于 0
使用场景
基于Decorator
强大的作用,我们能够完成各种场景的需求,下面简单列举几种:
使用react-redux
的时候,如果写成下面这种形式,既不雅观也很麻烦
class MyReactComponent extends React.Component {}
export default connect(mapStateToProps, mapDispatchToProps)(MyReactComponent);
通过装饰器就变得简洁多了
@connect(mapStateToProps, mapDispatchToProps)
export default class MyReactComponent extends React.Component {}
将mixins
,也可以写成装饰器,让使用更为简洁了
function mixins(...list) {
return function (target) {
Object.assign(target.prototype, ...list);
};
}
// 使用
const Foo = {
foo() { console.log('foo') }
};
@mixins(Foo)
class MyClass {}
let obj = new MyClass();
obj.foo() // "foo"
下面再讲讲core-decorators.js
几个常见的装饰器
1.@antobind
autobind
装饰器使得方法中的this
对象,绑定原始对象
import { autobind } from 'core-decorators';
class Person {
@autobind
getPerson() {
return this;
}
}
let person = new Person();
let getPerson = person.getPerson;
getPerson() === person;
// true
2.@readonly
readonly
装饰器使得属性或方法不可写
import { readonly } from 'core-decorators';
class Meal {
@readonly
entree = 'steak';
}
var dinner = new Meal();
dinner.entree = 'salmon';
// Cannot assign to read only property 'entree' of [object Object]
3.@deprecate
deprecate
或deprecated
装饰器在控制台显示一条警告,表示该方法将废除
import { deprecate } from 'core-decorators';
class Person {
@deprecate
facepalm() {}
@deprecate('功能废除了')
facepalmHard() {}
}
let person = new Person();
person.facepalm();
// DEPRECATION Person#facepalm: This function will be removed in future versions.
person.facepalmHard();
// DEPRECATION Person#facepalmHard: 功能废除了