es6语法

es6语法

let和const命令

let

  1. 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()判断某个元素是否在数组中

用于判断一个数组是否包含一个特定的值,根据情况,它能够返回 truefalse

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...inObject.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 对象必定处于以下三种状态之一:

  1. 待定(pending):初始状态,既没有被兑现,也没有被拒绝
  2. 已兑现(fulfilled):操作成功完成
  3. 已拒绝(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>

resolvereject

1.resolve : 将Promise对象的状态从 Pending(进行中) 变为 Fulfilled(已成功)

2.reject : 将Promise对象的状态从 Pending(进行中) 变为 Rejected(已失败)

3.resolvereject 都可以传入任意类型的值作为实参,表示 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模块主要功能由两个命令构成:exportimport

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

deprecatedeprecated装饰器在控制台显示一条警告,表示该方法将废除

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

deprecatedeprecated装饰器在控制台显示一条警告,表示该方法将废除

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: 功能废除了
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值