es6学习笔记(b站尚硅谷课程)

291 阅读19分钟

es6阮一峰老师文档:es6.ruanyifeng.com/

image.png

ES6 新特性

1.let变量声明以及声明特性

<script>
    // 声明变量
    let a;
    let b,c,d;
    let e = 100, g = 'love', f = [];

    // 1.变量不能重复声明--报错
    let str = '小小'
    let str = '亮亮'  
    // 2.块级作用域(全局,函数,eval)
    // 在 if else while for循环中都是局部有效
    {
        let girl = '杨燕'
    }
    console.log(girl)  // 报错
    // 3.不存在变量提升
    consol.log(song)   // 报错
    let song = '恋爱达人'  // 如果是 var 定义,输出结果是 undefined,不报错,所以var存在变量提升

    // 4.不影响作用域链
    {
        let school = '尚硅谷'
        function fn(){
            console.log(school)
        }
        fn()  // 在fn中找不到school会往上找
    }
</script>
//5.暂时性死区
var tmp = 123;
if (true) {
  tmp = 'abc'; // ReferenceError
  let tmp;
}

上面代码中,存在全局变量tmp,但是块级作用域内let又声明了一个局部变量tmp,导致后者绑定这个块级作用域,所以在let声明变量前,对tmp赋值会报错。

总之,在代码块内,使用let命令声明变量之前,该变量都是不可用的。这在语法上,称为“暂时性死区”。

“暂时性死区”也意味着typeof不再是一个百分之百安全的操作。

typeof x; // ReferenceError
let x;

作为比较,如果一个变量根本没有被声明,使用typeof反而不会报错。

typeof undeclared_variable // "undefined"

上面代码中,undeclared_variable是一个不存在的变量名,结果返回“undefined”。所以,在没有let之前,typeof运算符是百分之百安全的,永远不会报错。现在这一点不成立了。

image.png

image.png

2. const 声明变量以及特点

const 声明常量,常量就是值(内存地址)不能变化的量。

<script>    
    const SCHOOL = '尚硅谷'
    // 1.一定要赋初始值
    const A; // 报错  
    // 2.一般常量使用大写(潜规则)
    const A = 10
    // 3.常量的值不能修改
    SCHOOL = 'atguigu'  //报错
    // 4.块级作用域,只在声明所在的块级作用域内有效。
    // 5.对于数组和对象的元素修改,不算是对常量的修改,不会报错
    const ARR = ['tom','daming','tony']
    ARR.push('liming')   
    // 6.const命令声明的常量也是不提升,同样存在暂时性死区,只能在声明的位置后面使用。
</script>

3.变量的解构赋值

es6 允许按照一定模式从数组和对象中提取值,对变量进行赋值,这被称为解构赋值。

<script>
    // 1.数组的结构
    const F4 = ['小沈阳','刘能','赵四','宋小宝']
    let [one,two,three,four] = F4
    console.log(one)
    console.log(two)
    console.log(three)
    console.log(four)
    // 2.对象的解构
    const zhao = {
        name:'赵四',
        age:60,
        xiaopin:function(){
            console.log('我可以演小品')
        }
    }
    let {name,age,xiaopin} = zhao
    console.log(name)  // 赵四
    console.log(age)   // 60
    console.log(xiaopin) 
    xiaopin()  // 我可以演小品
    
    let {name:myName, age:Myage} = zhao // myName myage属于别名
    console.log(myName)
    
    // 也可以单独解构一个变量
    const {name} = zhao
</script>

如果解构不成功,变量的值就等于undefined

4.模板字符串

es6引入新的声明字符串的方式 ``

<script>
    // 1.声明
    let str = `I am a str`
    console.log(str,typeof str) //I am a str  string
    // 2.内容中可以直接出现换行
    let str = `<ul>
            <li>沈腾</li>
            <li>玛丽</li>
            <li>魏翔</li>
            <li>艾伦</li>
            </ul>`;
    // 3.变量拼接
    let lovest = '赵丽颖'
    let out = `${lovest}是最棒的`  // 模板字符串可以解析变量   
</script>

模板字符串中嵌入变量,需要将变量名写在${}之中。

5.简化对象写法

es6允许在大括号里面,直接写入变量和函数,作为对象的属性和方法。

<script>
    let name = 'atguigu'
    let change = function(){
        console.log('改变自己')
    }
    cosnt school = {
        name,  // 这里其实是 name:name,简写了
        change,
        // improve:function(){
        //   console.log('提高')
        //}
        // 方法简写
        improve(){
            console.log('提高')
        }
    }  
</script>

6.箭头函数以及声明特点

ES6 允许使用 箭头 (=>) 定义函数

<script>
    //声明一个函数
    // let fn = function(){
    // }
    let fn = (a,b) => {
          return a + b;
    }
    //调用函数
    let result = fn(1, 2);
    console.log(result);
    // 1.this是静态的,this始终指向函数声明时所在作用于下的 this 的值
    function getName(){
        console.log(this.name)
    }
    let getName2 = () => {
        console.log(this.name);
    }   
    //设置 window 对象的 name 属性
    window.name =  '尚硅谷'
    const school = {
        name:"ATGUIGU"
    }
    //直接调用,结果都是尚硅谷
    // 因为这两个函数都定义在全局作用域下
    // getName();  
    // getName2();
    // call方法调用,不能改变箭头函数的this指向
    getName.call(school)  // ATGUIGU
    getName2.call(school)  // 尚硅谷
    
    // 2. 不能作为构造实例化对象
    let Person = (name, age) => {
        this.name = name;
        this.age = age
    }
    let me = new Person('xiao',30)
    console.log(me)    // 报错
    
    // 3.不能使用 arguments 变量
    let fun = () => {
        console.log(arguments)
    }
    fn(1,2,3)  // 报错
    
    // 4.箭头函数的简写
    // (1) 省略小括号,当形参有且只有一个的时候
    // (2) 省略花括号,当代码只有一条语句的时候,此时 return 必须省略
    // 而且语句的执行结果就是函数的返回值
    let result = n => n * n
    console.log(pow(8))
</script>

由于大括号被解释为代码块,所以如果箭头函数直接返回一个对象,必须在对象外面加上括号,否则会报错。

6.1箭头函数使用注意点

(1)箭头函数没有自己的this对象(详见下文)。

(2)不可以当作构造函数,也就是说,不可以对箭头函数使用new命令,否则会抛出一个错误。

(3)不可以使用arguments对象,该对象在函数体内不存在。如果要用,可以用 rest 参数代替。

(4)不可以使用yield命令,因此箭头函数不能用作 Generator 函数。

上面四点中,最重要的是第一点。对于普通函数来说,内部的this指向函数运行时所在的对象,但是这一点对箭头函数不成立。它没有自己的this对象,内部的this就是定义时上层作用域中的this。也就是说,箭头函数内部的this指向是固定的,相比之下,普通函数的this指向是可变的。

举例说明:

var name = '南玖'
var person = {
    name: 'nanjiu',
    say: function() {
        console.log('say:',this.name)
    },
    say2: () => {
        console.log('say2:',this.name)
    }
}
person.say() // say: nanjiu
person.say2() // say2: 南玖

这里第一个say定义的是一个普通函数,并且它是作为对象person的方法来进行调用的,所以它的this指向的就是person,所以它应该会输出say: nanjiu

而第二个say2定义的是一个箭头函数,我们知道箭头函数本身没有this,它的this永远指向它定义时所在的上层作用域,所以say2this应该指向的是全局window,所以它会输出say2: 南玖

我们也可以通过Babel 转箭头函数产生的 ES5 代码来证明箭头函数没有自己的this,而是引用的上层作用域中this

// ES6
function foo() {
  setTimeout(() => {
    console.log('id:', this.id);
  }, 100);
}

// ES5
function foo() {
  var _this = this;
  setTimeout(function () {
    console.log('id:', _this.id);
  }, 100);
}

作者:前端南玖
链接:juejin.cn/post/706994…

6.2箭头函数实践

setTimeout是window的方法。setTimeout函数调用的代码运行在与所在函数完全分离的执行环境上,这会使得this指向的是window对象。

如果想要让setTimeout中的this不指向window,常用方法如下:
方法一:使用之前重新指向到一个变量,然后操作这个新的变量 617c5d2e2497e7a11a244750fa52f83.png

方法二:使用ES6的箭头函数 image.png
最后一句话写错了!!应该是:这个this指向定义时所在的上层作用域,也就是function。

案例2
image.png

image.png

总结:

  • 箭头函数适合与 this 无关的回调,比如定时器,数组的方法回调
  • 箭头函数不适合 与 this 有关的回调,比如事件回调,对象的方法

7.函数参数的默认值设置

es6允许给函数参数赋值初始值。

<script>
    // 1. 形参初始值,具有默认值的参数,一般位置要靠后(潜规则)
    function add(a,b,c=10){
        return a + b + c
    }
    let res = add(1,2)
    console.log(res) // 13
    
    // 2.与解构赋值结合
    // 如果传了实参的值就用实参的值,如果没有传就用默认形参的值
    // 这里解构赋值,不用考虑顺序
    function connect({host="127.0.0", username, password, port}){
        console.log(host,username,password,port)
    }
    connect({
        host:'localhost',
        username:'root',
        passwordL:'123',
        port:'8080'
    })  
</script>

8.rest 参数(剩余参数)

es6引入 rest 参数,用于获取函数的实参,用来代替 arguments

<script>
    // es5 获取实参的方式
    function date(){
        console.log(arguments)
    }
    date('白纸','墨水')
    // rest 参数
    function date2(...args){
        console.log(args)  //结果是数组
        // 所以可以使用filter some every map等方法
    }
    date2('白纸','墨水')
    // 而且 rest参数必须放到参数最后
    function fn(a, b, ...args){
        console.log(a)   // 1
        console.log(b)   // 2
        console.log(args)   // [3,4,5,6]
    }
    fn(1,2,3,4,5,6)
</script>

9.扩展运算符之三个点 ...

扩展运算符(spread)是三个点(...)。它好比 rest 参数的逆运算,将一个数组转为用逗号分隔的参数序列。

<script>
    // 声明一个数组
    cosnt tfboys = ['王源','王俊凯','易烊千玺']
    // 声明一个函数
    function chunwan(){
        console.log(arguments)
    }
    chunwan(tfboys)    // ['王源','王俊凯','易烊千玺']
    
    chunwan(...tfboys)   // 0:"王源"  1:"王俊凯"  2:"易烊千玺"
    // 等价于 chunwan('王源','王俊凯','易烊千玺')
</script>

扩展运算符应用

9.1 数组的合并

    let kuaizi = ['晓阳','旺旺']
    let fenghuang = ['曾毅','玲花']
    let kf = [...kuaizi,...fenghuang]
    console.log(kf)  //  ['晓阳','旺旺','曾毅','玲花']
    // 浅拷贝,使用的时候需要注意
    
    // 方法二
    kuaizi.push(...fenghuang)

举例--合并数组:

const a1 = [{ foo: 1 }];
const a2 = [{ bar: 2 }];

// ES5 的合并数组
const a3 = a1.concat(a2);

// ES6 的合并数组
const a4 = [...a1, ...a2];

console.log(a3[0] === a1[0] )   // true
console.log(a4[0] === a1[0] )   // true

a3[0].bar = 3
console.log(a3[0])    //{ foo: 1, bar: 3 }
console.log(a1[0])    //{ foo: 1, bar: 3 }

上面代码中,a3a4是用两种不同方法合并而成的新数组,但是它们的成员都是对原数组成员的引用,这就是浅拷贝。如果修改了引用指向的值,会同步反映到新数组。 补充:如果数组里面有引用类型的话,扩展运算符也只是浅拷贝。

浅拷贝与深拷贝的区别:www.cnblogs.com/yy1234/p/73…
另一篇文章:浅拷贝与深拷贝的区别

9.2 数组的克隆

const str = ['E','F','G']
const sstr = [...str]   //等价于 ['E','F','G']
const [...sstr] = str;
console.log(sstr)  //  ['E','F','G']
console.log(str === sstr)  // false

上面的两种写法,sstr 都是 str 的克隆,再修改 sstr 不会对 str 产生影响。克隆是完全创造出一个新的对象,有自己的新的内存地址,只不过初始信息是和原对象是一样的。

9.3 将伪数组转成真正的数组

const divs = document.querySelectorAll('div')  // NodeList(3)[div,div,div]  原型是Object
const divArr = [...divs]
console.log(divArr)  // [div,div,div]

10. Symbol的介绍与创建

es6引入了一种新的原始数据类型Symbol,表示独一无二的值。它是JS语言的第七种数据类型,是一种类似于字符串的数据类型。

10.1 symbol的创建

Symbol特点

  • Symbol 的值是唯一的,用来解决命名冲突的问题
  • Symbol 值不能与其他数据进行运算
  • Symbol 定义的对象属性不能使用 for…in 循环遍历,但是可以使用 Reflect.ownKeys 来获取对象的所有键名
<script>
    // 创建 Symbol
    let s = Symbol()
    console.log(s, typeof s) // Symbol()  "symbol"
    let s2 = Symbol('尚硅谷')
    let s3 = Symbol('尚硅谷')
    console.log(s2 === s3)  // false
    // 第二种创建方法 Symbol.for 
    let s4 = Symbol.for('尚硅谷')
    let s5 = Symbol.for('尚硅谷')
    console.log(s4, typeof s4)  // Symbol(尚硅谷)  "symbol"
    console.log(s4 === s5)  // true
    // 重新使用同一个 Symbol 值,Symbol.for()方法可以做到这一点
    // Symbol.for()与Symbol()这两种写法,都会生成新的 Symbol。
    // 它们的区别是,前者会被登记在全局环境中供搜索,后者不会。
    // Symbol.for()不会每次调用就返回一个新的 Symbol 类型的值,而是会先检查给定的key是否已经存在,如果不存在才会新建一个值。
    
    // 不能与其他数据进行运算
    // let res = s + 100  // 报错
    // let res = s > 100  // 报错    
</script>

Symbol()函数的参数只是表示对当前 Symbol 值的描述,因此相同参数的Symbol函数的返回值是不相等的。主要是为了在控制台显示,或者转为字符串时,比较容易区分。

javascript 七种数据类型口诀:USONB(you are so niubility)

  • u——undefined
  • s——string、symbol
  • o——object
  • n——null、number
  • b——boolean

10.2 对象添加Symbol类型的属性

给对象添加方法方式一: symbol保证了up和down的属性名是独一无二的,所以添加进去也不怕有属性名冲突。

image.png

image.png

给对象添加方法方式二:

image.png

image.png

10.3 Symbol的内置属性

除了定义自己使用的 Symbol 值以外,ES6 还提供了 11 个内置的 Symbol 值,指向语言内部使用的方法。
image.png image.png

Symbol.hasInstance:当其他对象使用 instanceof 运算符,判断是否为该对象的实例时,会调用这个方法。 image.png

Symbol.isConcatSpreadable:对象的 Symbol.isConcatSpreadable 属性等于的是一个布尔值,表示该对象用于Array.prototype.concat()时,是否可以展开。 image.png image.png image.png

11.迭代器介绍

11.1 Iterator(遍历器)概念

JavaScript 原有的表示“集合”的数据结构,主要是数组(Array)和对象(Object),ES6又添加了MapSet。这样就有了四种数据集合,还可以组合使用它们,定义自己的数据结构,比如数组的成员是MapMap的成员是对象。这样就需要一种统一的接口机制,来处理所有不同的数据结构。

Iterator 的作用有三个:一是为各种数据结构,提供一个统一的、简便的访问接口;二是使得数据结构的成员能够按某种次序排列;三是 ES6 创造了一种新的遍历命令for...of循环,Iterator 接口主要供for...of消费。

工作原理:

  • 先创建一个指针对象,指向当前数据结构的起始位置
  • 第一次调用对象的next方法,指针自动指向数据结构的第一个成员
  • 接下来不断调用next方法,指针一直往后移动,直到指向最后一个成员
  • 每次调用next方法就会返回一个包含value和done属性的对象(value属性是当前成员的值,done属性是一个布尔值,表示遍历是否结束。)

11.2默认 Iterator 接口

Iterator 接口的目的,就是为各种不同的数据结构提供统一的访问机制。任何数据结构只要部署 Iterator 接口,就可以完成遍历操作(即依次处理该数据结构的所有成员)。

原生具备 Iterator 接口的数据结构如下。

  • Array
  • Map
  • Set
  • String
  • TypedArray
  • 函数的 arguments 对象
  • NodeList 对象

变量xiyou是一个数组,原生就具有遍历器接口,部署在xiyouSymbol.iterator属性上面。所以,调用这个属性,就得到遍历器对象。 5253a53dafcc3f5227d1528662477c4.png 注:需要自定义遍历数据的时候,要想到迭代器。

对于原生部署 Iterator 接口的数据结构,不用自己写遍历器生成函数,for...of循环会自动遍历它们。除此之外,其他数据结构(主要是对象)的 Iterator 接口,都需要自己在Symbol.iterator属性上面部署,这样才会被for...of循环遍历。

11.3迭代器自定义遍历对象

//声明一个对象
const data={
    name:'zzl',
    lis:[
        'wxl',
        'll',
        'hll'
    ],
    //自己给某些结构加上iterator接口
    [Symbol.iterator](){
        //索引变量
        let index=0;
        let _this=this;
        return {//返回一个指针对象,即创建一个指针对象
            next:function(){ //创建对象的next方法
                // 返回一个包含value和done属性(是否完成)的对象
                if(index < _this.lis.length){
                    const result = { value:_this.lis[index], done:false};
                    index ++;
                    return result;
                }else{                   
                    return { value:undefined, done:true};
                }
            }
        };
    }
}
//自定义遍历这个对象
// 如果不用迭代器自定义遍历对象的话,下面方法会报错,因为data是Object对象,不能遍历
for(let v of data){
    console.log(v);
}

12.生成器函数声明与调用

生成器就是一个特殊的函数,是异步编程新的解决方案。分别通过以下代码可知运行逻辑。

<script>
    function * gen(){
        console.log('hello generator')
    }
    let iterator = gen()
    iterator.next()   // hello generator 
</script>

image.png

image.png image.png

12.1生成器函数的参数传递

next方法可以传入实参。

第二次调用next的实参将作为第一个yield的整体返回结果。 image.png

12.2生成器函数实例1

//  1s 后输出111   2s后输出222  3s后输出333
// 回调地狱
// setTimeout(() => {
//       console.log(111);
//       setTimeout(() => {
//          console.log(222);
//             setTimeout(() => {
//                console.log(333);
//             }, 3000);
//        }, 2000);
// }, 1000);
function one(){
    setTimeout(()=>{
        console.log(111);
        a.next();//定时器运行完调用下一个,实现了异步编程
    },1000)
}
function two(){
    setTimeout(()=>{
        console.log(222);
        a.next();
    },2000)
}
function three(){
    setTimeout(()=>{
        console.log(333);
        a.next();
    },3000)
}
function * fun(){
    yield one();
    yield two();
    yield three();
}
//调用生成器函数
let a=fun();
a.next();

12.3生成器函数实例2

function one(){
    setTimeout(()=>{
        let data='用户数据';
        a.next(data);//第二次调用next的实参将作为第一个yield的整体返回结果
    },1000)
}
function two(){
    setTimeout(()=>{
        let data='订单数据';
        a.next(data);
    },1000)
}
function three(){
    setTimeout(()=>{
        let data='商品数据';
        a.next(data);
    },1000)
}
function *fun(){
    let users= yield one(); //用户数据 作为第一个yield的整体返回结果
    console.log(users);
    let orders= yield two();
    console.log(orders);
    let goods= yield three();
    console.log(goods);
}
//调用生成器函数
let a=fun();
a.next();

13.Promise

Promise是es6引入的异步编程的新解决方案。语法上,Promise是一个构造函数,用来封装异步操作并可以获取其成功或失败的结果。

  • Promise.prototype.then 方法

then方法是定义在原型对象Promise.prototype上的。它的作用是为 Promise 实例添加状态改变时的回调函数。第一个参数是resolved状态的回调函数,第二个参数是rejected状态的回调函数,它们都是可选的。

then方法返回的是一个新的Promise实例(注意,不是原来那个Promise实例)。因此可以采用链式写法,即then方法后面再调用另一个then方法。

  • Promise.prototype.catch 方法

它是.then(null, rejection).then(undefined, rejection)的别名,用于指定发生错误时的回调函数。

阮一峰--promise对象

13.1 promise基本语法

const p = new Promise(function(resolve, reject){
  setTimeout(function(){
    // let data = '成功'
    // resolve(data)
    let err = '失败'
    reject(err)
  },2000)
})
//调用 promise 对象的 then方法
p.then(function(value){
  console.log(value)  //promise状态变为成功后then会调用第一个回调函数
},function(reason){
    console.log(reason) //promise状态变为失败后then会调用第二个回调函数
})

13.2 Promise 读取文件

// 1.引入 fs 模块
const fs = require('fs')

// 2.调用方法读取文件
fs.readFile('./res.md',(err,data)=>{
  // 如果失败,则抛出错误
  if (err) throw err
  // 如果没有出错,则输出内容
  console.log(data.toString())
})

// 3.使用 Promise 封装
const p = new Promise(function(resolve, reject){
  fs.readFile('./res.md',(err,data)=>{
    if (err) {
      reject(err)
    }
    resolve(data)
  })
})
//调用 promise 对象的 then方法
p.then(function(value){
  console.log(value.toString())  
},function(reason){
    console.log('读取失败')
})

13.3 Promise 封装 Ajax请求

const p = new Promise(function(resolve,reject){
  // 1.创建对象
  const xhr = new XMLHttpRequest();
  // 2.初始化
  xhr.open('GET','https://apiopen.top/getJoke')
  // 3.发送
  xhr.send()
  // 4.绑定事件
  xhr.onreadystatechange = function(){
    // 判断
    if(xhr.readyState === 4){
      // 判断响应状态码 200 - 299
      if(xhr.status >= 200 && xhr.status < 300){
        //表示成功
        resolve(xhr.response)
      }else{
        //表示失败
        reject(xhr.status)
      }
    }
  }
})
p.then(function(value){
  console.log(value)  
},function(reason){
  console.log(reason)
})

14. Set 集合 与 API

Es6 提供了新的数据结构 set(集合),它类似于数组,但成员的值都是唯一的,集合实现了iterator 接口,所以可以使用 扩展运算符 和 for…of… 进行遍历,集合的属性和方法:

(1)size 返回集合的元素个数
(2)add 增加一个新元素,返回当前集合
(3)delete 删除元素,返回 boolean 值
(4)has 检测集合中是否包含某个元素,返回 boolean

<script>
    let s = new Set()
    let s2 = new Set(['大事儿','小事儿','喜事儿','小事儿'])
    // 元素个数
    console.log(s2.size) // Set(3)
    // 添加新元素
    s2.add('好事儿')
    // 删除元素
    s2.delete('大事儿')
    // 检测元素
    console.log(s2.has('喜事儿'))
    // 清空集合
    s2.clear()
    
    for(let v of s2){
        console.log(v)
    }
    // set解构的实例与数组一样,也有forEach方法
    // 用于对每个成员执行某些操作,没有返回值
    s2.forEach(item => console.log(item))
</script>

集合实践

14.1 数组去重

<script>
    let arr1 = [1, 2, 2, 3, 3, 3, 4, 1, 2];
    let res1 = [...new Set(arr1)];
    console.log(res1); // [ 1, 2, 3, 4 ]
</script>

14.2交集

<script>
    let arr1 = [4,5,6,5,6];
    let arr2 = [1,2,3,4,6];
    // 将arr2转成数组,用filter方法
    let result = [...new Set(arr1)].filter( item => {
        // s2还是set类型数据,用has方法做判断
        let s2 = new Set(arr2)
        if(s2.has(item)){
            return true
        }else{
            return false
        }
    }) 
    // 简化写法
    let result = [...new Set(arr1)].filter( v => new Set(arr2).has(v))
    console.log(result)
</script>

14.3并集

<script>
    let arr1 = [4,5,6,5,6];
    let arr2 = [1,2,3,4];
    let arr3 = [...new Set([...arr1, ...arr2])]
    console.log(arr3)  // [4,5,6,1,2,3]
</script>

4.差集

<script>
    let arr1 = [4,5,6,5,6];
    let arr2 = [1,2,3,4,6];
    // 主体不同结果不同
    let result = [...new Set(arr1)].filter( v => !(new Set(arr2).has(v)))
    console.log(result)   // [5]
    let result2 = [...new Set(arr2)].filter( v => !(new Set(arr1).has(v)))
    console.log(result2)  // [1,2,3]
</script>

15. Map的介绍与API

ES6提供了Map数据结构。它类似于对象,也是键值对的集合。但是‘键’的范围不限于字符串,各种类型的值(包括对象)都可以当作键。Map也实现了 iterator 接口,所以可以使用 扩展运算符 和 for…of… 进行遍历。Map的属性和方法:

(1)size 返回Map的元素个数
(2)set 增加一个新元素,返回当前Map
(3)get 返回键名对象的键值
(4)has 检测Map中是否包含某个元素,返回 boolean值
(5)clear 清空集合,返回 undefined

<script>
    let m = new Map()
    // 添加元素
    m.set('name','尚硅谷')
    m.set('change',function(){
        console.log('改变世界')
    })
    let key = {
        school:'atguigu'
    }
    m.set(key,['北京','上海','广州'])
    console.log(m)
</script>

image.png

 for(let v of m){
        console.log(v)
 }

image.png

16. class类

ES6 提供了更接近传统语言的写法,引入了 Class(类)这个概念,作为对象的模板。通过class关键字,可以定义类。

基本上,ES6 的class可以看作只是一个语法糖,它的绝大部分功能,ES5 都可以做到,新的class写法只是让对象原型的写法更加清晰、更像面向对象编程的语法而已。

 function phone(brand, price){
     this.brand = brand
     this.price = price
 }
 phone.prototype.call = function(){
     conosle.log('打电话')
 }
 let p = new phone('华为',5999)
 console.log(p)

等价于:

    class phone{
        constructor(brand, price){
             this.brand = brand
             this.price = price
        }
        // 方法必须使用该语法,不能使用 es5 的对象完整形式
        call(){
            conosle.log('打电话')
        }
    }
    let p = new phone('华为',5999)
    console.log(p)

16.1 class静态成员

image.png

等价于:

image.png

16.2 ES5 构造函数继承

 function phone(brand, price){
     this.brand = brand
     this.price = price
 }
 phone.prototype.call = function(){
     conosle.log('打电话')
 }
 // 只能手机
 function SmartPhone(brand, price, color, size){
     phone.call(this, brand, price) //改变this指向,此时this指向SmartPhone
     this.color = color
     this.size = size
 }
 // 设置子级构造函数的原型
 SmartPhone.prototype = new phone()
 SmartPhone.prototype.constructor = SmartPhone //可写可不写
 // 声明子类的方法
 SmartPhone.prototype.photo = function(){
     console.log('拍照')
 }
 const s = new SmartPhone('小米',3999,'紫色','128G')
 console.log(s)

image.png

16.3 ES6 class的类继承

    class phone{
        // 构造方法
        constructor( brand, price){
             this.brand = brand
             this.price = price
        }
        // 父类的成员属性
        call(){
            conosle.log('打电话')
        }
    }
    class SmartPhone extends phone{
        // 构造方法
        constructor( brand, price, color, size){
            super( brand, price) //等价于phone.call(this, brand, price)
            this.color = color
            this.size = size
        }
        photo(){
            console.log('拍照')
        }
        // ES6 子类对父类方法的重写(完全重写)
        call(){
            console.log('我可以进行视频通话')
        }
    }
    const s = new SmartPhone('小米',3999,'紫色','128G')
    console.log(s)
    s.call()

16.4 ES6 class中的getter和setter方法

image.png image.png

17. ES6的数值扩展

<script>
    // Number.EPSILON 是js表示的最小精度
    function equal(a, b){
        if(Math.abs(a - b) < Number.EPSILON){
            return true
        }else{
            return false
        }
    }
    console.log( 0.1 + 0.2 === 0.3)   // false
    console.log( equal(0.1 + 0.2, 0.3))  // true
    // 1. 二进制 与 八进制
    let b = 0b1010  // 10
    let o = 0o7777   // 511
    let d = 100
    let x = 0xff    // 255
    // 2. NuMBER.isFinite 检测一个数值是否为有限数
    console.log(Number.isFinite(100))   // true
    console.log(Number.isFinite(100 / 0))  // false
    // 3. Number.isNaN 检测一个数值是否为NaN
    console.log(Number.isNaN(123))  // false
    // 4. Number.parseInt  Number.parseFloat 字符串转整数
    console.log(Number.parseInt('12.88256756876'))  // 12
    console.log(Number.parseFloat('12.8825675aaa')) // 12.8825675
    // 5. Number.isInteger 判断一个数是否为整数
    // 6. Math.trunc 将数字的小数部分抹掉
    Math.trunc('123.456') // 123
    Math.trunc(true) //1
    Math.trunc(false) // 0
    Math.trunc(null) // 0
    // 7. Math.sign 判断一个数到底为正数 负数 还是 0 
    Math.sign(-5) // -1
    Math.sign(5) // +1
    Math.sign(0) // +0
    Math.sign(-0) // -0
    Math.sign(NaN) // NaN
</script>

18. ES6的对象方法扩展

18.1 Object.is()

ES5 比较两个值是否相等,只有两个运算符:相等运算符(==)和严格相等运算符(===)。它们都有缺点,前者会自动转换数据类型,后者的NaN不等于自身,以及+0等于-0。JavaScript 缺乏一种运算,在所有环境中,只要两个值是一样的,它们就应该相等。

ES6 提出同值相等算法,用来解决这个问题。Object.is就是部署这个算法的新方法。它用来比较两个值是否严格相等,与严格比较运算符(===)的行为基本一致。

    Object.is('foo', 'foo')  // true
    Object.is({}, {})  // false

不同之处只有两个:一是+0不等于-0,二是NaN等于自身。

+0 === -0 //true
NaN === NaN // false
Object.is(+0, -0) // false
Object.is(NaN, NaN) // true

18.2 Object.assign()

Object.assign()方法用于对象的合并,将源对象(source)的所有可枚举属性,复制到目标对象(target)。

const target = { a: 1, b: 1 };
const source1 = { b: 2, c: 2 };
const source2 = { c: 3 };
Object.assign(target, source1, source2);
console.log(target) // {a:1, b:2, c:3}

Object.assign()方法的第一个参数是目标对象,后面的参数都是源对象。

注意,如果目标对象与源对象有同名属性,或多个源对象有同名属性,则后面的属性会覆盖前面的属性。

注意点

(1)浅拷贝

Object.assign()方法实行的是浅拷贝,而不是深拷贝。也就是说,如果源对象某个属性的值是对象,那么目标对象拷贝得到的是这个对象的引用。

const obj1 = {a: {b: 1}, c:1, d:true};
const obj2 = Object.assign({}, obj1);
obj1.a.b = 2;
console.log(obj2.a.b)   // 2 
console.log(obj2.c)     // 1 
console.log(obj2.d)     // true

上面代码中,源对象obj1的a属性的值是一个对象,Object.assign()拷贝得到的是这个对象的引用。这个对象的任何变化,都会反映到目标对象上面。

(2)同名属性的替换

对于这种嵌套的对象,一旦遇到同名属性,Object.assign()的处理方法是替换,而不是添加。

const target = { a: { b: 'c', d: 'e' } }
const source = { a: { b: 'hello' } }
console.log(Object.assign(target, source))// { a: { b: 'hello' } }

一些函数库提供Object.assign()的定制版本(比如 Lodash 的_.defaultsDeep()方法),可以得到深拷贝的合并。

18.3 Object.setPrototypeOf()

Object.setPrototypeOf方法的作用与__proto__相同,用来设置一个对象的原型对象(prototype),返回参数对象本身。

const obj = { a:10 }
const cities = {
    xiaoqu:['北京','上海']
}
Object.setPrototypeOf( obj, cities)
console.log(Object.getPrototypeOf(obj))
console.log(obj)  

image.png

19. 模块化

19.1模块化的好处

  • 防止命名冲突
  • 代码复用
  • 高维护性

19.2模块化规范产品

ES6 之前的模块化规范有:

  • CommonJS => NodeJS、Browserify
  • AMD => requireJS
  • CMD => seaJS

19.3ES6 模块化语法

模块功能主要由两个命令构成:exportimport

  • export 命令用于规定模块的对外接口
  • import 命令用于输入其他模块提供的功能

19.3.1 模块导出数据语法

(1)分别暴露
export let uname = 'Rick';
export let sayHello = function () {
    console.log('Hi, bro!');
}
(2)统一暴露
let uname = 'Rick';
let sayHello = function () {
    console.log('Hi, bro!');
}
// 合并导出
export { uname, sayHello };
(3)默认暴露
export default {
    uname: 'Rick',
    sayHello: function () {
        console.log('Hi, bro!');
    }
}

19.3.2 模块导入数据语法

(1)通用导入方式
import * as m1 from './js/m1.js';
(2)解构赋值导入
import { uname, sayHello } from './js/m1.js';
// 有重复名可以设置别名
import { uname as uname2, sayHello as sayHello2 } from './js/m2.js';
// 配合默认暴露
import {default as m3} from "./src/js/m3.js";
(3)简便方式导入,针对默认暴露
import m3 from "./src/js/m3.js";

19.3.3 ES6 使用模块化方式二

将文件导入都写进一个 app.js 文件中,然后在里面写入要导入的模块。app.js 中的内容如下:

import * as m1 from './js/m1.js';
import * as m2 from './js/m2.js';
import * as m3 from './js/m3.js';
console.log(m1);
console.log(m2);
console.log(m3);

在 index.html 中引入 app.js 文件内容:

<script src="./app.js" type="module"></script>

19.4 babel对ES6模块化代码转化

它能将js的新语法转化为旧语法,以便更好的兼容。babel转码器

ES7 新特性

1. Array.prototype.includes

includes 方法用来检测数组中是否包含某个元素,返回布尔值。 实例方法 : includes( )

没有该方法之前,通常使用数组的indexOf方法,检查是否包含某个值。

if (arr.indexOf(el) !== -1) {
  // ...
}
[NaN].indexOf(NaN)  // -1

indexOf方法有两个缺点,一是不够语义化,它的含义是找到参数值的第一个出现位置,所以要去比较是否不等于-1,表达起来不够直观。二是,它内部使用严格相等运算符(===)进行判断,这会导致对NaN的误判。

2. 指数运算符

在 ES7 中引入指数运算符 **,用来实现幂运算,功能与 Math.pow(a, b) 结果相同。

2 ** 3 // 8
Math.pow(2, 3) // 8

ES8 新特性

1. async 和 await

async 和 await 两种语法结合可以让异步代码像同步代码一样

1.1 async函数

  • async 函数的返回值为 promise 对象
  • promise 对象的结果由async函数执行的返回值决定
// async 函数
async function fn(){
  // 1.返回一个字符串
  // return '尚硅谷'
  // 只要返回结果不是一个 Promise 类型的对象
  // 则函数返回结果就是一个成功的 Promise <resolved>

  // 2. 抛出错误,返回的结果是一个失败的 Promise <rejected>
  // throw new Error('出错了')

  // 3. 返回结果是 Promise 对象
  return new Promise((resolve,reject)=>{
      // resolve('成功的回调')
      reject('失败的回调')
  })

}
const res = fn()
// 哪怕是字符串,返回值也是 Promise 对象
console.log(res)
res.then(value => {
  console.log(value)
},err => {
  console.warn(err)
})

1.2 await表达式

  • await 必须写在 async 函数中
  • await 右侧的表达式一般为 promise 对象
  • await 返回的是 promise 成功的值
  • await 的 promise 失败了,就会抛出异常,需要通过 try…catch 捕获异常
  • 当右接一个 Promise 对象,则 await 表达式会阻塞后面的代码,等待当前 Promise 对象 resolve 的值。
const p = new Promise((resolve,reject)=>{
      // resolve('成功的回调')
      reject('失败的回调')
})
async function main(){
  try{
      let response = await p
      console.log(response)
  }catch (e){
      console.log(e)
  }
}
main()

综合 asyncawait 而言。await 必须结合 async 使用,而 async 则不一定需要 awaitasync 会将其后的函数的返回值封装成一个 Promise 对象,而 await 会等待这个 Promise 完成,然后返回 resolve 的结果。当这个 Promise 失败或者抛出异常时,需要时使用 try-catch 捕获处理。

1.3 async 和 await结合读取文件内容

需求:先读取用户数据 user,然后读取订单数据 order,最后读取商品数据 goods。

对于这种异步操作很容易想到使用 Promise,代码如下:

const fs = require('fs')
let p = new Promise((resolve, reject) => {
  fs.readFile('./files/user.md', (err, data) => {
    if (err) reject(err)
    resolve(data)
  })
})
p.then(value => {
  return new Promise((resolve, rejecet) => {
    fs.readFile('./files/order.md', (err, data) => {
      if (err) rejecet(err)
      resolve([value, data])
    })
  })
}, reason => {
  console.log(reason)
}).then(value => {
  return new Promise((resolve, reject) => {
    fs.readFile('./files/goods.md', (err, data) => {
      if (err) reject(err)
      value.push(data)
      resolve(value)
    })
  })
}, reason => {
  console.log(reason)
}).then(value => {
  console.log(value.join('\n'))
}, reason => {
  console.log(reason)
})

但是,使用 Promise 链式调用虽然避免了回调地狱,但这种链式调用过多难免引起代码复杂,看起来不直观。可以使用 asyncawait 方法优化,代码如下:

const fs = require('fs')
function readUser() {
  return new Promise((resolve, reject) => {
    fs.readFile('./files/user.md', (err, data) => {
      if (err) reject(err)
      resolve(data)
    })
  })
}
function readOrder() {
  return new Promise((resolve, reject) => {
    fs.readFile('./files/order.md', (err, data) => {
      if (err) reject(err)
      resolve(data)
    })
  })
}
function readGoods() {
  return new Promise((resolve, reject) => {
    fs.readFile('./files/goods.md', (err, data) => {
      if (err) reject(err)
      resolve(data)
    })
  })
}
async function read() {
  let user = await readUser()
  let order = await readOrder()
  let goods = await readGoods()
  console.log([user, order, goods].join('\n'))
}
read()

1.4 async 和 await发送 ajax请求

function sendAjax(url) {
  return new Promise((resolve, reject) => {
    const xhr = new XMLHttpRequest()
    xhr.open('get', url)
    xhr.send()
    xhr.onreadystatechange = function () {
      if (xhr.readyState === 4) {
        if (xhr.status >= 200 && xhr.status < 300) {
          resolve(JSON.parse(xhr.response))
        }
        reject(xhr.status)
      }
    }
  })
}
async function main() {
  let res = await sendAjax('http://poetry.apiopen.top/sentences')
  let poem = res.result.name + '——' + res.result.from
  document.body.innerText = poem
}
main()

这里封装的ajax还不能体现 async-await 的作用所在,因为没有出现多个 ajax 请求。在有多个 ajax 请求并且后续的请求依赖于前一个请求的结果的时候,async-await 的优点就体现出来了。

2. Object.values 和 Object.entries

ES5中,Object.keys方法,返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历(enumerable)属性的键名

var obj = { foo: 'bar', baz: 42 };
Object.keys(obj)   // ["foo", "baz"]

Object.values方法返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历(enumerable)属性的键值

const obj = { foo: 'bar', baz: 42 };
Object.values(obj)   // ["bar", 42]

Object.entries()方法返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历(enumerable)属性的键值对数组

const obj = { foo: 'bar', baz: 42 };
Object.entries(obj)    // [ ["foo", "bar"], ["baz", 42] ]

返回的是一个数组,这样就可以使用 for...of 遍历了。

for(let [k,v] of Object.entries(obj)){
  console.log([k,v])
}

ES9 新特性(暂时未学习)

1.扩展运算符与rest参数

2.正则扩展-命名捕获分组

3.正则扩展-反向断言

4.正则扩展-dotAll模式

ES10 新特性

1.对象扩展方法 Object.fromEntries

<script>
    // 二维数组转化为对象
    const result = Object.fromEntries([
        ['name','尚硅谷'],
        ['course','软件工程']
    ])
    console.log(result)
    
    // Map数据结构
    const m = new Map()
    m.set('name','ATGUIGU')
    const result2 = Object.fromEntries(m)
    console.log(result2)
    
    // 对象转化为二维数组
    const arr = Object.entries({
        name:'尚硅谷'
    })
    console.log(arr)
</script>

image.png

2.字符串方法扩展 trimStart-trimEnd

trimStart()消除字符串头部的空格,trimEnd()消除尾部的空格。它们返回的都是新字符串,不会修改原始字符串。

const s = '  abc  ';                     
s.trim() // "abc"
s.trimStart() // "abc  "
s.trimEnd() // "  abc"

除了空格键,这两个方法对字符串头部(或尾部)的 tab 键、换行符等不可见的空白符号也有效。

2.数组方法扩展 flat和flatMap

数组的成员有时还是数组,Array.prototype.flat()用于将嵌套的数组“拉平”,变成一维的数组。该方法返回一个新数组,对原数据没有影响。

    [1, 2, [3, 4]].flat()   // [1, 2, 3, 4]
    [1, 2, [3, [4, 5]]].flat(2)   // [1, 2, 3, 4, 5]

flat()默认只会“拉平”一层,参数即为深度,默认为1。

    const arr = [1,2,3,4]
    const result = arr.flatMap( item => [item * 10])
    console.log(result)  // [ 10, 20, 30, 40]

flatMap()方法对原数组的每个成员执行一个函数(相当于执行Array.prototype.map()),然后对返回值组成的数组执行flat()方法。该方法返回一个新数组,不改变原数组。

3. Symbol.prototype.description

Symbol 值的实例属性description,直接返回 Symbol 值的描述。

<script>
    // 创建 Symbol
    const sym = Symbol('foo');
    console.log(sym.description)  // "foo"
</script>

ES11 新特性

1. 私有属性

class添加了私有属性,方法是在属性名之前使用#表示。

<script>
    class Person{
        // 公有属性
        name;
        // 私有属性
        #age;
        #weight;
        // 构造方法
        constructor(name, age, weight){
            this.name = name
            this.#age = age
            this.#weight = weight
        };
        log(){
            console.log(this.#age)
        }
    }
    const girl = new Person('小小',18,'46kg')
    console.log(girl)
    // console.log(girl.#age) // 报错,不能访问私有属性
    girl.log()  // 18
</script>

2. Promise.allSettled方法

Promise.allSettled()方法接受一个数组作为参数,数组的每个成员都是一个 Promise 对象,并返回一个新的 Promise 对象。只有等到参数数组的所有 Promise 对象都发生状态变更(不管是fulfilled还是rejected),返回的 Promise 对象才会发生状态变更,且都是成功状态。

<script>
    const p1 = new Promise((resolve, reject)=>{
        setTimeout(()=>{
                resolve('成功')
        },1000)
    })
    const p2 = new Promise((resolve, reject)=>{
    	setTimeout(()=>{
        	reject('失败')
        },1000)
    })
    // 调用 allSettled方法
    const res = Promise.allSettled([p1, p2])
    console.log(res)
    // all方法,全部成功返回结果才成功,有一个失败则返回结果是失败
    const res2 = Promise.all([p1, p2])
    console.log(res2)
</script>

image.png

3. String.prototype.matchAll方法(正则-暂时未学习)

阮一峰 String.prototype.matchAll( )

4. 可选链操作符

可选链 ?. 是一种访问嵌套对象属性的安全的方式。即使中间的属性不存在,也不会出现错误。 原则:如果可选链 ?. 前面的部分是 undefined 或者 null,它会停止运算并返回该部分。

<script>
let user = {
    address: {
    }
}
console.log( user?.address?.street ); // undefined(不报错)
</script>

5. 动态 input

const btn = document.getElementById('btn');
// import()返回一个promise对象,返回的是所有暴露的方法
btn.onclick = function(){
  import('./hello.js').then(module => {
    module.hello();
}

5. BigInt类型

image.png

6.绝对全局对象globalThis

全局对象提供可在任何地方使用的变量和函数。默认情况下,这些全局变量内置于语言或环境中。 在浏览器中,它的名字是 window,对 Node.js 而言,它的名字是 global,其它环境可能用的是别的名字。 ES11中 globalThis 被作为全局对象的标准名称加入到了 JavaScript 中,所有环境都应该支持该名称。所有主流浏览器都支持它。 使用场景: 假设我们的环境是浏览器,我们将使用 window。如果你的脚本可能会用来在其他环境中运行,则最好使用 globalThis

Array的扩展方法

(一)构造函数方法:Array.from()

(1)将类数组或可遍历对象转换为真正的数组。

(2)方法还可以接受第二个参数,作用类似于数组的map方法,用来对每个元素进行处理,将处理后的值放入返回的数组。

    var arraylist = {
    	'0':'张三',
        '1':'里斯',
        '2':'汪芜',
        'length':3,
    }
    var arr = Array.from(arraylist)
    console.log(arr)  // ['张三','里斯','汪芜']
    
    var list = {
        '0':1,
        '1':2,
        '2':3,
        'length':3,
    }
    var newArr = Array.from(list, item => item*2)
    console.log(newArr)  // [2,4,6]

(二)实例方法:find()

用于找出第一个符合条件的数组成员,如果没有找到就返回undefined。

    let arr = [{
        id: 1,
        name:'张三'
    },{
        id: 2,
        name:'李四'
    }]
    let newArr = arr.find((item,index) => item.id ==2 )
    console.log(newArr)

(三)实例方法:findIndex()

用于找出第一个符合条件的数组成员的位置,如果没有返回-1。

    let arr = [2,1,3,5]
    let index = arr.findIndex((item,index) => item>4)
    console.log(index) // 3

(四)实例方法:includes()

表示某个数组是否包含给定的值,返回布尔值。

    let arr = [2,4,6,1,3]
    let flag = arr.includes(3)
    console.log(flag)  // true

String的扩展方法

(一)startsWith()

表示参数字符串是否在原字符串的头部,返回布尔值。

(二)endsWith()

表示参数字符串是否在原字符串的尾部,返回布尔值。

    let str = 'hello world!'
    console.log(str.startsWith('he'))  // true
    console.log(str.endsWith('!'))  // true

(三)repeat()

repeat方法表示将原字符串重复n次,返回一个新的字符串。

    let str = 'hello'
    let newStr = str.repeat(3)
    console.log(newStr)