「掘金日新计划 · 8 月更文挑战」的第5天——JavaScript 的进阶版——es6(下)

81 阅读28分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第5天,点击查看活动详情

image.png

又到了周一,双休的日子总是过的那么快,我有时候在想人周而复始的去做一件事能坚持多久,一月,一年还是十年,好像都没有确切的回答.....好啦,扯远了,其实做什么事都需要自己不断的学习和练习,在实践中总结。

正文开始啦

四、ES6新增的内置对象及已有对象的扩展

1.字符串的扩展方法及模板字符串

1)ES5处理Unicode的缺陷

image.png//乱码 总结:es5无法处理(含有两个双字节)四个字节的字符

2)ES6加强了对Unicode的支持
在ES5中我们知道JavaScript 允许采用\uxxxx形式表示一个字符,其中xxxx表示字符的 Unicode 码点。这种表示法只限于码点在\u0000~\uFFFF之间的字符。超出这个范围的字符,必须用两个双字节的形式表示,但是ES5却无法正确的识别这个有两个字节组成的字符。
ES6中,JavaScript增加了对超出\u0000~\uFFFFUnicode范围的字符支持
ES6的方案:将超过两个字节的组成的字符的码点放在一对花括号里就可以正确的识别。

3)符串的遍历接口
image.png 注:传统的for循环无法遍历字符串,最好使用for-of遍历循环

4)扩展的API

方法描述
includes(string, position)判断字符串中是否包含指定字符串,返回值是布尔值
startsWith(string, position)判断字符串的开头是否包含指定字符串,返回值是布尔值
endsWith(string, position)判断字符串的尾部是否包含指定字符串,返回值是布尔值
repeat(n)repeat() 方法返回一个新字符串,表示将院字符串重复n 次。
 字符串补全第一个参数是补全后的字符串⻓度,第二个参数是用于补全的  字符串
padStart(length, str)用于头部补全
padEnd(length, str)用于尾部补全

代码:

// 判断字符串是否包含指定字符串的几个方法
includes(string, position)   判断字符串中是否包含指定字符串,返回值是布尔值
startsWith(string, position)   判断字符串的开头是否包含指定字符串,返回值是布尔值  第n个位后有,true
endsWith(string, position)  判断字符串的尾部是否包含指定字符串,返回值是布尔值  第n个位前有,true
{
    let str = '123miaozai321'
    console.log('includes', str.includes('miaozai'))//true
    console.log('startsWith', str.startsWith('miaozai'3))//true
    console.log('endsWith', str.endsWith('miaozai'7))//false
}

// repeat() 方法返回一个新字符串,表示将院字符串重复n 次。(es5只能用for)
{
    let str = '1Nick23'
    str = str.repeat(3)
    console.log('repeat',str)//repeat 1miazai231miazai231miazai23
}

 
//字符串补全:第一个参数是补全后的字符串⻓度,第二个参数是用于补全的字符串
//padStart(length, str)  用于头部补全
{
    let str = 'Apple'
    str = str.padStart(8'asdad')
    console.log('padStart', str)//padStart asdApple
}
//padEnd(length, str)    用于尾部补全
{
    let str = 'Apple'
    str = str.padEnd(8'asdad')
    console.log('padEnd', str)//padEnd Appleasd
}

5)模板字符串

{
// 语 法
const name = "miazai"
const age = 18
const hobbies = "唱歌,睡觉,打游戏"

// ES5写法
const str1 = '我的名字是'+name+',我今年'+age+'岁,我喜欢'+hobbies
console.log(str1)//我的名字是miazai,我今年18岁,我喜欢唱歌,睡觉,打游戏

// ES6写法  使用$
const str2 = `我的名字是${name},我今年${age}岁,我喜欢${hobbies}`
console.log(str2)//我的名字是miazai,我今年18岁,我喜欢唱歌,睡觉,打游戏
}

使用模板字符串的注意事项:

  • 在模板字符串中如需使用反引号,反引号前要用反斜杠转义
  • 使用模板字符串表示多行字符串时,所有的空格和缩进都会被保留在输出之中 
      
  • 模板字符串中引入变量,要用 ${变量名} 这样的形式引入才可以
  • 大括号中的值不是字符串时,将按照一般的规则转为字符串。比如,大括号中是一个对象将默认调用对象的toString方法
  • 模板字符串中的${.  } 大括号内部可以放入任意的 JavaScript 表达式,可以进行运算、可以引用对象属性、可以调用函数、可以甚至还能嵌套,甚至还能调用自己本身。

2.ES6和ES7之数组的扩展方法及扩展运算符

1)扩展运算符的使用(只是举例常用例子)

  • 复制数组
{
const list =[1,2,3,4,5]
let list2=list
list.push(6)
console.log(list2)//[1, 2, 3, 4, 5, 6]
}

这样赋值是不行的,因为他们只是指向了同一个对象。在list赋值给list2时,改变list数组,就会该改变list2的数组,这样就不是复制数组了

{
//正确做法
let list =[1,2,3,4,5]
let list2 =[...list]
console.log(list2)//[1, 2, 3, 4, 5]
}
  • 分割数组
// 分割数组
{
const totalList = [1'a''b''c']
let [, ...strList] = totalList
console.log(strList)//[1, 2, 3, 4, 5]
}
  • 将数组转化成参数传递给函数
{
// 给函数传递参数
function add(x, y) {
    return x + y
    }

let addList = [12]
console.log(add(...addList));//3
}

2)新增的常用的方法(es6/es7新的api)

  • fill 用于将一个固定值替换数组的元素。
    一般用于数据的初始化
    注:这边需要先复制这个数组,如果直接.fill就会改变原数据,与数组的push没什么区别
{
const list = [1, 2, 3, 4, 5]
let list2 = [...list].fill(3)
console.log('list2',list2)
//list2 (5) [3, 3, 3, 3, 3]
}

//不想替换全部数据只想替换其中几个(第二项和倒数第二项)
//可以传入两个参数满足需求
//先复制.fill(第一个参数想要填充的值,第二参数是开始填充位置(索引),第三个参数代表的是停止填充位置。

{
// fill
const list = [1, 2, 3, 4, 5]
let list2 = [...list].fill(3)
let list3 = [...list].fill(314)
console.log('list2', list2)
console.log('list3', list3
//list2(5[3, 3, 3, 3, 3] ,list3(5[1, 3, 3, 3, 5]
}

  • find  findIndex(es5想找数据要遍历)寻找数据

findIndex() 方法返回传入一个测试条件(函数)符合条件的数组第一个元素位置。
findIndex() 方法为数组中的每个元素都调用一次函数执行:
(1)当数组中的元素在测试条件时返回 true 时,
findIndex() 返回符合条件的元素的索引位置,之后的值不会再调用执行函数。 (2)如果没有符合条件的元素返回 -1
(3) findIndex() 对于空数组,函数是不会执行的。
(4) findIndex() 并没有改变数组的原始值**

{
  // find findIndex
  const list = [
    { title: "es6" ,    id:1},
    { title: "react",   id: 2 },
    { title: "vue" ,    id:3},
    { title: "webpack", id: 4},
  ];
  let result = list.find(function (item) {
    return item.title === "webpack";
  });
  console.log('result',result)//result {title: "webpack", id: 4}
  
  let resultIndex = list.findIndex(function (item) {
    return item.title === "webpack";
  });
  console.log('resultIndex', resultIndex);//resultIndex 3
}
  • Includes

和indexof有点相像,但是比indexof好一点,因为indexof判断数组里面是否有数据的话,还要加上判断条件>-1(因为它返回的是一个数组,里面能查到数据的下标,所以额外加判断条件),但是inclides不用担心,直接返回一个布尔类型true flase

注:includes只能判断一些简单类型的数据,

数组,对象(引用类型)则无法判断,判断的话只能判断它们的指针,必须要定义一个变量来存一下对象,后面才能判断。

{
 const list =[1,2,3,4,5,6]
 let result = list.includes(2)
 console.log('result',result)// result true
}
  • Flat  展开运算符
//如果这个数组是两层数组,可以使用扩展运算符+concat进行展开
//Concat(es5)如果后面是一个数相当于push的操作,如果是数组相当于是一个合并的操作
{
// flat展开数组的操作
const list = [123, ['2nd'456, ['3rd'78]]]
let flatList = [].concat(...list)
console.log(flatList)

// 默认只展开第一层数组
let flatList2 = list.flat(2)
console.log('flat', flatList2)
}

image.png

3.ES6数组中map及reduce方法

1)map
假设从后台接受了数组,是一些JSON数据,后台规定0未上线,1已上线在前端的表格显示的需求,我们应该怎么做呢?
思想:
定义一个video来接收改变后的数据,做一个数据映射 里面的第一个参数是一个回调函数,回调函数接收的第一个参数是item,这个item就是里面的数据元素了,第二个参数this,用于改变回调函数里面this的指向,(这里不需要,这个this随便传,传的就是我们想要改变this指向的那个对象就可以了),然后定义自己的字段,用name来接收,更改想要的字段

{
    const json=[{title:'js',status:1},{title:'vue',status:0},{title:'react',status:1}]
    let video =json.map(function(item){
      let obj ={}
      Object.assign(obj,item)
      obj.status=item.status?'已上线':'未上线'
      return obj
    })
    console.log('json',json)
    console.log('video',video)
   }

image.png

2)Reduce  

语法:arr.reduce(callback(accumulator, currentValue[, index[, array]])[, initialValue])

参数含义
acc是一个返回值,每次回调之后的返回值
currentValue当前数组进行回调的值
currentIndex数组里面的位置
Array回调的数组
initialValue初始化返回值 可选参数 如果有acc返回值就是acc,如果没有它,acc返回的初始值就是数组的第一项
{
 //统计字符串里面每个字母出现的次数
  const letterList = "abcadefrd";
  const result = letterList.split("").reduce(function (acc, cur) {
    acc[cur] ? acc[cur]++ : (acc[cur] = 1);
    return acc;
  }, {});
  console.log(result);
}

image.png

{
  //展开数组  当后台是多层数组的时候,不知道要展开多少层的时候
  const list = [1, ["2nd", 2, 3, ["3rd", 4, 5]], ["2nd", 6, 7]];
  const deepFlat = function (list) {
    return list.reduce(function (acc, cur) {
        //判断当前的对象是不是一个数组,如果是数组的话就展开,如果不是数组的话,就合并
      return acc.concat(Array.isArray(cur) ? deepFlat(cur) : cur);
    }, []);
  };
  let flatList = deepFlat(list);
  console.log("reduce-flat", flatList);
}

image.png

4.ES6和ES8之对象的新特性及新增方法

1)扩展运算符的使用

{
    // 复制对象
    const obj={name:'miaozai',hobby:'撸猫'}
    const initObj={color:'red'}
    let hobbyObj={...obj}
    console.log('hobbyObj',hobbyObj)//hobbyObj {name: "miaozai", hobby: "撸猫"}

    //设置对象默认值
    let obj2={...obj,name:'miazai1'}
    console.log('obj2',obj2)//obj2 {name: "miazai1", hobby: "撸猫"}

    //合并对象
    let obj3={...obj,...initObj}
    console.log('obj3',obj3)//obj3 {name: "miaozai", hobby: "撸猫", color: "red"}

}



坑点:简单类型的时候,使用扩展运算符是没有问题的,但是如果扩展运算符展开对象以后还是一个对象的话,我们复制的只是一个指针。

2)属性初始化的简写

{
    // 属性键值相等的时候可以直接简写
    let name ='miazai'
    let age =18
    //es5的写法
    let es5Obj={
        name:name,
        age:age,
        sayHello:function(){
            console.log('this is es5Obj')
        }
    }
    console.log('es5',es5Obj)
    es5Obj.sayHello()
    
    //es6的写法
    let es6Obj={
        name,
        age,
        sayHello(){
            console.log('this is es6Obj')
        }
    }
    console.log('es5',es6Obj)
    es6Obj.sayHello()
}

image.png

3)可计算的属性名

{
    // 直接使用变量定义对象
    let key='name'
    //Es5需要先声明在再定义
    let es5Obj={}
    es5Obj[key]='miaozai'
    //Es6直接定义变量的对象
    let es6Obj={
        [key]:'miaozai1'
    }
    console.log('es5Obj',es5Obj)
    console.log('es6Obj',es6Obj)
}

image.png

4)新增的方法

  • object.is ()
    判断两个值是否相等,只要值相等就返回true
{
   let result=Object.is(NaN,NaN)
   console.log('result',result)
}
//与“===”类似,区别:NaN=?NaN  object.is返回true“===”返回flase
  • object.assgin()
    拷贝  当叠加很多对象的时候不好用,但是一层对象很好用( 如果复制的对象或者是数组,复制的只是一个指针)
{
  const person={name:'喵崽',age:18,info:{height:180}}
  let person2={}
  Object.assign(person2,person)
  person.info.height=160
  console.log('person2',person2)
}

image.png    

  • Away.from
    将类数组对象转换成真正的数组
{
  const str ='hello'
  const strList =Array.from(str)
  console.log('strList',strList)//strList (5) ["h", "e", "l", "l", "o"]
}
  • Object
{
    //Object.keys遍历对象里面所有的键
    //Object.values遍历对象里面所有的值
    //Object.entries遍历对象里面所有的键-值
    const json ={name:'miaozai',age:18,hobby:'play computer games'}
    let obj={}
    for(const key of Object.keys(json)){
        obj[key]=json[key]
    }
    console.log(obj)
}

image.png

5.Map与WeakMap结构的特点

1)背景
JavaScript中的对象,实质就是键值对的集合(Hash结构),但是在对象里却只能用字符串作为键名。在一些特殊的场景里(使用DOM元素或者是对象数组来作为键名的时候)就满足不了我们的需求了,正因为此,Map这一数据提出了,它是JavaScript中的一种更完善Hash结构。

2)Map对象
(1)作用:
用于保存键值对,任何值(对象或者原始值)都可以作为一个键或一个值

(2)使用介绍

// 通过构造函数创建一个Map 
var m = new Map();

(3)内置API

属性/方法作用例子
size返回键值对的数量m.size
clear()清除所有键值对m.clear()
has(key)判断键值对中是否有指定的键名,返回值是布尔值m.has(key)
 get(key)获取指定键名的键值对,如不存在则返回undefined m.get(key)
set(key, value) 添加键值对,如键名已存在,则更新键值对m.set(key, value)
delete(key)删除指定键名的键值对m.delete(key)
{
 let map = new Map()
 //添加键值对,如键名已存在,则更新键值对
 map.set([1,2,3],'number')
 console.log('map',map)


 let map2 =new Map([['name','miazai'],['sex','female']])
 console.log('map2',map2)
 //返回键值对的数量
 console.log('map2.size',map2.size)

 map2.set('name','xiaobai').set('hobbies',['swiming','running'])
 console.log('map2',map2)

 //获取指定键名的键值对,如不存在则返回undefined
 console.log('get',map2.get('hobbies'))
 console.log('get2',map2.get('age'))

 //判断键值对中是否有指定的键名,返回值是布尔值
 console.log('has',map2.has('age'))
 
 //删除指定键名的键值对
 map2.delete('hobbies')
 console.log(map2)
}

image.png

3)WeakMap
1. 只接受对象作为一个键名,不接受其他类型的数据作为键名
2. 因为键名所指的对象不触发垃圾回收机制
3. 没有clear,没有size,无法遍历

{
  let weakmap = new WeakMap([[{ name: "hah" }, "jack"]]);
  console.log(weakmap);
}

image.png

使用场景:
在浏览器中获取dom元素,如果把这个dom(ulObj)放到这个键名weakmap当中,如果ulObj被销毁了,这个引用weakmap也会被销毁,{name: 'hah'}被垃圾回收

{
//主要使用于绑定dom元素
  const ulObj = document.getElementById("test");
  let obj = { name: "miaozai" };
  let array = [obj, "person"];
  array[0] = null;
}

这样声明数组,如果不在使用obj这个对象,必须手动进行删除引用才能触发垃圾回收这个机制,也就是强行将数组设为空因为它的引用随时会被删除,所以无法遍历,对应的API也没有

6.Set与WeakSet结构的特点

  • set

1)介绍
Set是ES6给开发者提供的一种类似数组的数据结构,可以理解为值的集合。它和数组的最大的区别就在于: 它的值不会有重复项。

2)基本使用

// 创 建
let set = new Set();
let set2 = new Set([1,2,3])
// 添加元素
set.add(1)

3)内置API

属性/方法作用例子
size返回成员个数s.size
clear()清除所有成员s.clear()
has(value)判断键值对中是否有指定的值,返回值是布尔值s.has(key)
delete(value)删除指定值s.delete(key)
{
let set =new Set(['1',2,3,4,5])
//添加元素
set.add(1)
console.log('set',set)
// 内部是使用Object.is() 同值相等
console.log('set.size',set.size)
}

image.png

{
    //属性判断has 删除属性delete  清空clear
    let set =new set()
    set.add({fruit:'apple'})
    console.log('has',set.has({fruit:'apple'}))//false两个对象的指针不一样
}


//用item存储一下
{
    //属性判断has 删除属性delete  清空clear
    let set =new set()
    const item ={fruit:'apple'}
    set.add(item)
    console.log('has',set.has(item))//true
}

4)用途

// 去 重
let arr = [1,2,2,3,4,4,4];
let s = new Set(arr);
//结果:Set {1,2,3,4}

let newArr = Array.from(s);
//结果:[1,2,3,4],完成去重

5)遍历

{
    const set =new Set([1,2,3,4,5])
    for(const value of set.value()){
        console.log(value)
    }
}

image.png

{ const set =new Set([1,2,3,4,5]) for(const value of set.entries()){ console.log(value) } }

image.png

  • WeakSet
    1.数组成员必须是对象
    2.WeakSet结构也提供了add( ) 方法,delete( ) 方法,has( )方法给开发者使用,作用与用法跟Set结构完全一致。
    3.WeakSet结构不可遍历。因为它的成员都是对象的弱引用,随时被回收机制回收,成员消失。所以WeakSet 结构不会有keys( ),values( ),entries( ),forEach( )等方法和size属性
{
  let obj = {};
  let weakset = new WeakMap();
  WeakMap.add(obj);
  console.log("weakset", weakset);
}

7.Map、Set与Array及Object间的区别

image.png

image.png

image.png

image.png

image.png

image.png

image.png

image.png

  • 类型转换

image.png

image.png

  • 数组和set

image.png

8.快速理解ES6中的代理Proxy和反射Reflect

1)代理Proxy
正如Proxy的英译"代理"所示,Proxy是ES6为了操作对象引入的API。它不直接作用在对象上,而是作为一种媒介,如果需要操作对象的话,需要经过这个媒介的同意。
Proxy代理的就是对象的一些操作(读取,设置)  可以进行拦截,进行自定义的处理

(1)声明代理对象

/* @params\
** target: 用Proxy包装的目标对象\
** handler: 一个对象,对代理对象进行拦截操作的函数,如set、get\
*/\
let p = new Proxy(target, handler)

(2)常用api

get拦截读取的操作用的是
set拦截设置对象的属性   
has拦截对象里面in操作,判断对象属性是否在对象里
deleteproperty拦截删除属性的操作    
ownkeys拦截遍历的操作

2)反射Reflect Reflect.has(obj, name)是 name in obj 指令的函数化,用于判断对象中是否有某一个属性, 返回值为布尔值

{

    let obj = {
        name'xiaobai',
        age'18',
        sex'male',
        hobbies'swimming'
    }
    console.log(Reflect.get(obj, 'name'))
    Reflect.set(obj,'name''Jack')
    console.log(obj.name)
    'name' in obj
    Reflect.has(obj, 'name')
}
//把object里面所有的方法放在Reflect里面,Proxy里面的方法Reflect里面都有,操作对象的时候,可以直接调用Reflect

注意:推荐Reflect来操作对象,而不是直接操作对象

3)Proxy与Reflect实现简单的双向数据绑定

image.png image.png

{
  // 获取dom元素
  const inputObj = document.getElementById("input");
  const txtObj = document.getElementById("txt"); // 初始化代理对象

  const obj = {}; // 配置代理选项
  const handler = {
    get: function (target, key) {
      return Reflect.get(target, key);
    },
    set: function (target, key, value) {
      if (key === "text") {
        //如果键名是text的时候
        //判断input的value是不是设置的值,
        inputObj.value = inputObj.value === value ? inputObj.value : value; //i标签的值变成改变后的值
        txtObj.innerHTML = value;
      }
      return Reflect.set(target, key, value); // 返回代理对象
    },
  };
  //代理
  let objProxy = new Proxy(obj, handler); //将代理对象,代理选项放入 // 给input添加键盘键入事件
  inputObj.addEventListener("keyup", function (e) {
    objProxy.text = e.target.value;
    console.log(objProxy); //每次输入的时候也是动态改变的
  });
  objProxy.text = "124";
}

五、函数的扩展、类的概念及模块化

1.函数的扩展

  • 函数参数可设置默认值
// 默认参数
//es5只能通过赋值操作
    function es5Print(x, y) {    
        y = y || 'world'
        console.log('es5', x + y)
    }
es5Print('hello')  //helloworld
//es5Print('hello','')
//缺陷:如果传入空的字符串即现在y为空,但是js的判断里面是非空的,不是非空的才将继续执行,如果是空的话会取world,输出还是hellow world,显然不是想要的

image.png

{

   function es6Print(x, y = 'world') {
        console.log('es6', x + y)
    }
    es6Print('hello''')
}
//hello
  • Rest参数
//rest是es6对函数提出的扩展
//在一个不定参数的情况下。设置一个rest数组+扩展运算符
{
    // rest
    function add(...rest) {  //在不确定函数的个数时
        let sum = 0
        console.log(rest)
        for (let value of rest) {
            sum += value
        }
        console.log(sum)
        // Array.prototype.method.apply(arrgument)  arrgument使用数组的方法    
}
    add(12345)
}
//15
  • 扩展运算符
{
    //扩展运算符
    console.log(...[12345])
}

//展开数组变成一个一个参数
//在函数调用的时候,可以传入...,可以展开对应的参数
  • 尾调用
{
    // 尾调用
    function step2(x) {
        console.log('尾调用', x)
    }
    function step1(x) {
        return step2(x)
    }
}
//在函数的最后面返回的是另一个函数
//作用:提高性能
//使用递归的时候可以使用尾调用

2.深入浅出箭头函数

注意事项:

  • 什么情况下使用箭头函数
  • 箭头函数没有argument

image.png//报错   如果使用var不会错

!小坑:
如果在函数里面定义了参数,这个参数在函数体内使用let再声明的话就会报错

//箭头函数是一个匿名函数
//声明函数想要指定变量来存取的话只能通过声明式函数来写
//通过声明量定义自变量
{
    // 声明函数
    const arrow = (x) => {
        console.log('箭头函数')
    }
   arrow()
}

特点:
1.更短的函数

//箭头函数只有一个参数,不写括号,直接写参数,只有一行语句可以省略花括号,函数体只有一个return语句,默认返回
{
    const arrow = x => x * 2
    const result = arrow(4)
    console.log(result)//8
}
注意:X*2加了花括号、//undefine

2.不绑定this

image.png//NAN

在setTimeout里面定义了function,this的指向发生了变化,settimeout是window下面的方法,方法里面的this指向的就是window,不能直接使用,可以利用箭头函数

{
    const fruit = {
        name'apple',
        price18,
        num3,
        sum() {
            window.setTimeout(() => { 
                console.log(this.num * this.price)
            }, 1000)
        }
    }
    fruit.sum()
}
////54    箭头函数不绑定this,也就是说箭头函数没有this,它的this取决于对象上一级的作用域里面,也就是大括号里面有个this

总结:

/**
 * 在JavaScript什么时候使用箭头函数
 * Object.method()调用的话就用普通函数进行申明,其他情况下都用箭头函数

 */

3.ES6中全方位理解类的概念

传统的javascript中只有对象,没有类的概念。它是基于原型的面向对象语言。原型对象特点就是将自身的属性共享给新对象。

new操作做了什么事?

  • 返回(产生)了一个新的对象
  • 将这个空对象的_ proto_指向了构造函数内部的prototype
  • 将构造函数的this绑定到这个对象上(即new创建的对象,其函数体内的this指向的是这个对象)
  • 返回到这个新对象上
const obj = {}
obj. proto = 构 造 函 数 .prototype; 构造函数.call(obj);
{

    // ES5的时候是通过构造函数来实现类的功能的
    function Person(name, age) {
        this.name = name
        this.age = age
    }

    //添加方法,一般添加在原型上面
    Person.prototype.sayHello = function () {
        console.log(`大家好, 我叫${this.name},我今年${this.age}岁了`)
    }

    //使用类形成对象实例
    const p = new Person('小明'17)
    console.log(p)
}

image.png

总结es5实现类的形式:new这个关键字做了几件事情 生成了一个对象,将this绑定在这个person对象上面,也就是说将这个对象实例this动态绑定这个构造函数上面,并且通过这个对象的原型指向这个构造函数的prototype,在构造函数的prototype写了一个方法可以共享到这个实例对象上面,可以发现,proto里面有个sayhello方法,也就是说对象和构造函数的prototype建立了联系,公用函数的功能 5)js中模块化开发(import和export)

//Es6
{
  // ES6改造ES5实现类的方法
  //通过class声明
  class Person {
    //构造函数constructor
    constructor(name, age) {
      this.name = name;
      this.age = age;
    } //方法
    sayHello() {
      console.log(`大家好, 我叫${this.name},我今年${this.age}岁了`);
    }
  }
  const p = new Person("小红", 17);
  console.log("class", p);
  console.log(typeof Person); //funcation  本质是个函数
}

{
  // 类的继承
  class Parent {
    constructor(name = "miaozai") {
      this.name = name;
    }
  }
  class Child extends Parent {
    constructor(name = "xiaobai") {
      // super要放在构造函数的最前面 作用:告诉父类我要修改的属性是什么,改写父类属性
      super(name);
      this.name = name;
    }
  }
  console.log("继承", new Child());
}

//类的get,set的使用
{
  class Person {
    constructor(name = "Nick") {
      this.name = name;
    }
    get fullName() {
      //get,set不是函数相当于是属性
      return this.name + "\xa0" + "Liu";
    }
    set fullName(value) {
      this.name = value;
    }
  }
  const p = new Person();
  console.log("get", p.fullName); //对象加属性相当于get操作  fullname是属性
  p.fullName = "Jack";
  console.log("set", p.name); //set属性不能是它本身,一定是其他的,不然循环报错
}

es6 class的概念
1.定义类  2.类的继承  3.子类向父类传递参数 4.静态方法 5.静态属性

{
  // 给类定义静态方法   只能构造函数里面使用
  class Person {
    constructor(name = "小白") {
      this.name = name;
    }
    static sayHello(obj) {
      console.log("my name is " + obj.name);
    }
  }
  //通过类使用方法
  const p = new Person("小花");
  Person.sayHello(p);
}

//在es6里面无法直接在一个类里面定义一个静态属性, 在类的外面进行挂载;
{
  // 定义静态属性
  class Person {
    constructor(name = "小白") {
      this.name = name;
    }
    static sayHello(obj) {
      console.log("my name is " + obj.name);
    }
  }
  //只能通过在类的外面定义
  Person.prop = "test";
  console.log(Person.prop);
}

es7提供了新的方法  可直接在类里面直接书写一个属性 需要下载插件 cnpm install babel-preset-es2017 ,在babel里面进行书写

{
  // 定义静态属性
  class Person {
    static prop = "test";
    constructor(name = "Nick") {
      this.name = name;
    }
    static sayHello(obj) {
      console.log("my name is " + obj.name);
    }
  }
  console.log(Person.prop);
}

4.js中模块化开发(import和export)

1)背景:
ES6之前是没有类的概念的,也就没有模块这一说法。理想情况下,开发者应该只注重核心业务的开发,对于其他有一定通用性的业务可以直接引入别人的代码,也就是模块。多人开发,本应如此。

2)提出的方案

  • CommonJS

Commonjs是作为Node中模块化规范以及原生模块面世的AMD和RequireJsAMD是"Asynchronous Module Definition"的缩写,意思就是"异步模块定义"。它采用异步方式加载模块,模块的加载不影响它后面语句的运行。所有依赖这个模块的语句, 都定义在一个回调函数中,等到所有依赖加载完成之后(前置依赖),这个回调函数才执行。

  • Import 引入模块 import test from './路径'
  • export导出模块
1.声明
let a = 3;
function sayHello() {
    console.log('default''hello')
}

2.导出
输出想要输出的变量
export default {
    a,
    sayHello
}

六、深入解析JavaScript中的异步编程

1.异步编程及JavaScript的异步实现

1)什么是同步

当一个”调用”发出时,在没有得到结果之前,这个”调用”就会阻塞后面代码的执行,得到结果的时候才会返回。换句话说,”调用者”要主动等待代码的执行结果,得到返回结果后,程序才会继续运行。

2)什么是异步

“调用”发出的时候,就直接返回了,对应的结果会通过状态、通知来告诉”调用者”或通过回调函数   处理这个调用。异步调用发出后,不会阻塞后面的代码。

3)为什么js导入异步概念? 

1.JavaScript是单线程的

2.同步代码会阻塞后面的代码

3.异步不会阻塞程序的运行

4)JavaScript中异步的实现

1. 回调函数:ajax的使用场景

2. setInterval和setTimeout  设置定时器

3. Promise

4. Generator

5. async

2.解决回调地狱提出的新方案—Promise

1).什么是回调地狱?

{
  // 回调地狱
  function ajax(cb) {
    setTimeout(() => {
      cb &&
        cb(() => {
          console.log("任务2");
        });
    }, 1000);
  }
  ajax((cb2) => {
    console.log("任务1");
    setTimeout(() => {
      cb2 && cb2();
    }, 1000);
  });
}

什么是回调地狱?

首先建立异步函数ajax,传入参数,回调函数,利用settimeout模拟异步请求,若回调函数存在执行,调用ajax,往里面传入一个回调函数,执行完任务1继续执行2,判断这个函数是否存在,cb2是在调用cd的条件下存在的,在执行第一次回调函数的时候要传入第二次回调函数

如果嵌套多层,代码臃肿,执行过程不明确

2).什么是Promise

Promise是一个对象,也可以说是一种编程思想。应用的场景就是"当xxx执行完毕的时候,then执行xxx动作"。Promise里不仅可以存放着异步的代码,也可以放同步的代码。

resolve:程序下一步执行的回调函数

reject:  程序出错要执行的回调函数

{
  // Promise改造回调函数
  function ajax() {
    return new Promise((resolve, reject) => {
      setTimeout(() => resolve(), 1000);
    });
  } // ajax().then(() => { //     console.log('任务1') // })
}


{
  //调用多层函数
  function ajax() {
    return new Promise((resolve, reject) => {
      setTimeout(() => resolve(), 1000);
    });
  }
  ajax()
    .then(() => {
      console.log("任务1");
      return new Promise((resolve) => {
        setTimeout(() => resolve(), 1000);
      });
    })
    //链式调用
    .then(() => {
      console.log("任务2");
    });
}

3).Promise的使用

1. 封装Promise 2. 捕获异常

{
  // 使用catch方法捕捉错误
  function judgeNumber(num) {
    return new Promise((resolve, reject) => {
      if (typeof num === "number") {
        resolve(num);
      } else {
        const err = new Error("请输入数字"); //生成错误对象
        reject(err);
      }
    });
  } // judgeNumber('2') //     .then(num => console.log(num)) //     .catch(err => console.log(err))
}
//Error:请输入数字

4).Promise的方法

Promise.all方法
需求:异步加载图片全部渲染到页面上,展示在页面上


{
    // Promise.all
    const imgUrl1 = 'tuyi1.png'
    const imgUrl2 = 'tuyi1.png'
    const imgUrl3 = 'tuyi1.png'

   
//获取图片
 function getImage(url) {
        return new Promise((resolve, reject) => {
            const img = document.createElement('img')//创建一个img标签
            img.src = url
            //onload判断图片加载完成
            img.onload = () => resolve(img)
            //出现异常捕获异常
            img.onerror = (err) => reject(err)
        })
}

//展示图片
function showImage(imgs) {
        imgs.forEach(item => {
            document.body.appendChild(item)
        })
    }

 Promise.all([getImage(imgUrl1), getImage(imgUrl2), getImage(imgUrl3)]).then(showImage)
// 判断promise函数是否执行完成,当e全部promise状态改变之后才会执行下一步

Promise.race
只要其中一个promise状态成功改变了就会执行下一步

{
  function showFirstImage(img) {
    document.body.appendChild(img);
  } // Promise.race用于希望多个Promise函数当中只要一个执行成功这种场景

  Promise.race([getImage(imgUrl2), getImage(imgUrl1), getImage(imgUrl3)]).then(
    showFirstImage
  );
}

3.JS的数据结构中统一的遍历接口Iterator和for...of循环

1.什么是Iterator

{
  // 什么是Iterator
  // 取出数据集合里的数据,通过遍历,Iterator提供了一个接口,通过for of调用这个接口,输出数据集合中的数据
  const arr = [1, 2, 3];
  const fn = arr[Symbol.iterator]();
  console.log(fn.next()); //第一次使用生成一个遍历器对象
  console.log(fn.next());
  console.log(fn.next());
  console.log(fn.next());
}

2)Iterator的作用

1.为不同的数据结构,提供一个统一的访问接口
2.将数据成员按照一定的顺序输出
3.提供的ES6的for...of的这个循环语句进行使用

3)什么结构具备原生lterator

  • 接口:
  • Array;
  • String
  • Set;
  • Map
  • 函数的argument对象
  • 本身就有Symbol.iterator属性,可以直接使用for...of

4)默认的Iterator接口: Symbol.iterator
本质:
是一个函数,就是当前的数据集合默认的遍历器生成函数,执行这个函数,就会返回一 个遍历器。
返回值:
返回一个遍历器对象。这个对象里的显著特点就是有一个next()方法。每次调用next都会返回一个描述当前成员的信息对象,具有value和done两个属性。

5)应用

{
    // 应用场景
    const obj = {
        color: 'red',
        price: 18,
        size: 'small',
        //使用for of循环必须要将iterator定义在symbol这个属性里面,这个属性返回的是一个函数,定义一个函数放在里面
        [Symbol.iterator]() {
            用于循环里面的key值
            let index = 0
            //通过es6里面的Object.values的方法取出对象里面的属性值,将this传入
            const values = Object.values(this)
            return {
                //Symbol.iterator必须返回一个next方法,才能通过for of进行调用
                里面有两个方法value(数据循环中的一个值),done(循环的状态)
                next() {
                    //判断循环是否结束
                    if (index < values.length) {
                        return {
                            value: values[index++],
                            done: false
                        }
                    } else {
                        return {
                            done: true
                        }
                    }
                }
            }
        }
    }

    for (const value of obj) {
        console.log(value)
    }
}

4.ES8之更直观的异步编程写法—Generator函数

1)什么是Generator
用于生成一个lterator

//定义generator函数   yield关键字(相当于return)

{
    const say = function* () {
        yield 'a'
        yield 'b'
        yield 'c'
    }
    //通过generator可以return多个值,通过next方法可以取到这些值

    const fn = say();//生成generator实例
    console.log(fn.next())  //{value:“a” , done:flase}
}
//generator函数返回的就是一个迭代器 ,要取到迭代器里面的值的话就需要next的方法


//声明对象
{
    let obj = {
        a: 1,
        b: 2,
        c: 3
    }

    obj[Symbol.iterator] = function* () {      //相当于定义好了generator函数
        for (const key of Object.keys(obj)) {
            yield obj[key]
        }
    }
    //遍历对象
    for (const value of obj) {
        console.log(value)  //1 2 3
    }
}

通过这个generator函数直接定义对象的Symbol.iterator,因为它生成的就是一个迭代器

//定义一个状态机
{
    // 任何时候都只有一定数量的状态
    const state = function* () {
        //在这三种状态里面一直切换 
        while (1) {
            yield 'success'
            yield 'fail'
            yield 'pending'//挂起
        }
    }
    const stateData = state()      //接受state实例
    console.log(stateData.next())
    console.log(stateData.next())
    console.log(stateData.next())
    console.log(stateData.next())
}
//长轮询,比如用户支付了订单,不断向后台查询用户是否付款了
{
  // 长轮询
  function fn1() {
    //一般使用Ajax进行访问后台操作的,返回来的就是promise,所以定义promise
    return new Promise((resolve) => {
      //传入执行函数
      setTimeout(() => {
        console.log("查询中");
        resolve({ code: 0 }); //如果查询成功
      }, 1000);
    });
  }

  //定义查询操作
  const getStatus = function* () {
    yield fn1();
  };

  //定义一个自动查询
  function autoGetStatus() {
    //生成generator实例,调用它可以得到一个迭代器
    const gen = getStatus();
    //用status保存状态,通过next调用方法
    const status = gen.next();
    //因为它返回的是一个promise实例,通过.value得到promise的实例,resolve之后传到then函数里面了,所以value.then就能取到这个code值,再用json(把他定义为res)接收它
    status.value.then((res) => {
      if (res.code === 0) {
        console.log("用户付款成功");
      } else {
        console.log("暂未付款");
        setTimeout(() => autoGetStatus(), 500); //等待一段时间继续执行
      }
    });
  }
  autoGetStatus();
}

如何用一个同步的写法写一个异步的编程

{
    const ajax = function* () {
        console.log('start')
        yield setTimeout(() => {
            console.log('异步任务执行结束')
        }, 100)
        console.log('end')
    }
    const runAjax = ajax()
    runAjax.next()   //取决于yield,总是比yield多一个
    runAjax.next()
}

这个不是我们想要的,因为start,end同时执行,next本身用于启动settimeout这个函数的,启动之后,定时器放在等待队列中,就直接 console.log('end')

{
  const ajax = function* () {
    console.log("start");
    yield function (cb) {
      setTimeout(() => {
        console.log("异步任务结束");
        cb && cb(); //回调函数存在并且调用
      }, 1000);
    };
    console.log("end");
  };

  const runAjax = ajax();
  //取出函数,通过next之后将它保存起来,因为next返回以后就是funcation
  const first = runAjax.next();
  //取出funcation,传入callback函数,告诉它可以执行了
  first.value(() => runAjax.next());
}

如何让settimeout执行完再执行end,通过回调函数进行通知?
第一步生成generator这个实例,第二步保存状态码first,定义变量,取出value值,这个value值就是funcation,通过回调函数,把next的操作放在回调函数里面,通过settimeout函数执行完成之后,再调用cb()这个回调函数,再调用这个next,就能按照一定的顺序输出

5.异步编程—async

1)什么是async
async是异步的简写,用于声明一个函数是异步函数,是generator函数的一个语法糖,可以自动调用next方法

{
  // async
  async function fn1() {
    await console.log(1);
    await console.log(2);
    await console.log(3);
  }
  fn1();
}
//1 2 3
{
  function fn1() {
    setTimeout(() => {
      console.log("任务1");
    }, 1000);
  }

  function fn2() {
    setTimeout(() => {
      console.log("任务2");
    }, 1000);
  }
  function fn3() {
    setTimeout(() => {
      console.log("任务3");
    }, 1000);
  }
  function init() {
    fn1();
    fn2();
    fn3();
  }
  init();
}

直接输出了任务一、任务二、任务三、这种写法就是将异步过程放到队列面,等到执行的时候同步输出

依次执行,使用async配合promise

{
  function fn1() {
    return new Promise((resolve) => {
      setTimeout(() => {
        console.log("任务1");
        resolve();
      }, 1000);
    });
  }
  function fn2() {
    return new Promise((resolve) => {
      setTimeout(() => {
        console.log("任务2");
        resolve();
      }, 1000);
    });
  }
  function fn3() {
    return new Promise((resolve) => {
      setTimeout(() => {
        console.log("任务3");
        resolve();
      }, 1000);
    });
  }
  async function init(fn1, fn2, fn3) {
    await fn1();
    await fn2();
    await fn3();
  }
  init(fn1, fn2, fn3);
}

2)await到底在等啥?

总结: await 等待的是一个表达式,这个表达式的计算结果是 Promise 对象或者其它值(换句话说,就是没有特殊限定)