要一直走下去

  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

ES6相关语法

一、let和const

使用let声明变量。

使用const声明常量。

二、Symbol数据类型

回顾一下:ES5的类型有这些

 Symbol介绍

/**
* Symbol是ES6中引入的一种新的基本数据类型,用于表示一个独一无二的值。
* 不能在调用Symbol时使用new关键字
* 在通过Symbol生成独一无二的值时可以设置一个标记,这个标记仅仅用于区分, 没有其它任何含义
*/
let a = Symbol()
let b = Symbol()
typeof a  //symbol
a===b  //false

Symbol.for &  Symbol.keyFor

/**
* 有时,我们希望重新使用同一个 Symbol 值,Symbol.for()方法可以做到这一点。
* Symbol.for方法根据string查找Symbol,没有就新建,类似单例模式。
* Symbol.keyfor方法根据Symbol查找string,找不到就是undefined
*/
let c = Symbol("nick")
let d = Symbol.for("nick")
let e = Symbol.for("nick")
c===d  //false
d===e  //true
Symbol.keyFor(c)  //undefined
Symbol.keyFor(d)  //字符串 "nick"

Symbol使用场景:

/**
* Symbol应用场景一: 使用Symbol来替代常量,可以保证常量是唯一的
*/
{
    const TYPE_AUDIO = 'AUDIO'
    const TYPE_VIDEO = 'VIDEO'
    const TYPE_IMAGE = 'IMAGE'
}
{
    const TYPE_AUDIO = Symbol()
    const TYPE_VIDEO = Symbol()
    const TYPE_IMAGE = Symbol()
}
/**
* Symbol应用场景二:作为对象属性名(key)能防止重名属性
* 跟普通key用法一样,只是这个key不能通过Object.keys或Object.getOwnPropertyNames获取,也不会被序列化
* 可以通过Object.getOwnPropertySymbols拿到Symbol的key
* 通过反射能拿到所有属性,以及属性的值
*/
let name = Symbol('name')
let obj = {
    [name]: 'Jack',
    age: 18,
    title: 'Engineer'
}

obj[name]  //'Jack'
name.description //'name'
Object.keys(obj)   //["age","title"]  Array
Object.getOwnPropertyNames(obj)   //['age', 'title']  Array
JSON.stringify(obj)   // '{"age":18,"title":"Engineer"}'  string
Object.getOwnPropertySymbols(obj)  //[Symbol(name)]  Array
Reflect.ownKeys(obj)  //["age","title",Symbol(name)]   Array

for(let k of Reflect.ownKeys(obj)){
    console.log(obj[k])  // 18  Engineer Jack
}

三、解构赋值

写的非常详细:  https://www.runoob.com/w3cnote/deconstruction-assignment.html

使用场景: 可以快速方便地从对象和数组中提取属性或数据到单独的变量中。

四、字符串的扩展方法和模板字符串

写的非常详细:https://www.runoob.com/w3cnote/es6-string.html

额外补充:

在ES5中我们知道JavaScript 允许采用\uxxxx形式表示一个字符,其中xxxx表示字符的 Unicode 码点。
这种表示法只限于码点在\u0000~\uFFFF之间的字符。超出这个范围的字符,必须用两个双字
节的形式表示,但是ES5却无法正确的识别这个有两个字节组成的字符。ES6中,JavaScript增加了
对超出\u0000~\uFFFFUnicode范围的字符支持。
ES6的方案:将超过两个字节的组成的字符的码点放在一对花括号里就可以正确的识别。
 
例子:
es5中,如果unicode编码超范围,输出结果不正确
// es5
{
    const str1 = 'a'     //用直接输入字符串
    const str2 = '\u20bb7'  //unicode编码输出字符串
    console.log(str2)
}
输出结果:

 es6中,用花括号括起来就能正确输出了:

{
    const str3 = '\u{20bb7}'
    console.log('str3', str3)
}

输出结果:

 

 如何遍历unicode超范围的字符串呢?必须用for-of遍历,用传统for循环遍历结果不正确

{
    
    const str3 = '\u{20bb7}'
    //传统for循环
    for (let i = 0; i < str3.length; i++) {
        console.log('for', str3[i])
    }
    // for of
    for (let word of str3) {
        console.log('for-of', word)
    }
}

输出结果:

*五、ES6和ES7之数组扩展和运算符扩展

写的非常详细包括示例:https://www.runoob.com/w3cnote/es6-array.html

扩展运算符“...”的使用
 1. 给函数传参
    function add(x, y) {
        return x + y
    }

    let addList = [1, 2]
    console.log(add(...addList));

2. 复制数组,浅拷贝

    const list = [1, 2, 3, 4, {a:1}]
    let list2 = [...list]
    console.log(list2)

3.分割数组

    const totalList = [1, 'a', 'b', 'c']
    let [, ...strList] = totalList
    console.log(strList)

4.合并数组

console.log([...[1, 2],...[3, 4]]); // [1, 2, 3, 4]

数组方法

Array.of():将参数中所有值作为元素形成数组

console.log(Array.of(1, 2, 3, 4)); // [1, 2, 3, 4]
// 参数值可为不同类型
console.log(Array.of(1, '2', true)); // [1, '2', true]
// 参数为空时返回空数组
console.log(Array.of()); // []

Array.from(obj) :将obj转换为数组,obj可以是类数组对象、可迭代对象、map、set、字符串、mapFn

//类数组对象
console.log(Array.from([1, 2])); // [1, 2]
//map
let map = new Map();
map.set('key0', 'value0');
map.set('key1', 'value1');
console.log(Array.from(map)); // [['key0', 'value0'],['key1', 'value1']]
//set
let arr = [1, 2, 3];
let set = new Set(arr);
console.log(Array.from(set)); // [1, 2, 3]
//字符串
let str = 'abc';
console.log(Array.from(str)); // ["a", "b", "c"]
//mapFn
console.log(Array.from([1, 2, 3], (n) => n * 2)); // [2, 4, 6]

includes() :数组是否包含指定值

find(fn) :查找数组中符合条件的元素,若有多个符合条件的元素,则返回第一个元素

findIndex(fn) :查找数组中符合条件的元素索引,若有多个符合条件的元素,则返回第一个元素索引

fill(a, i1, i2) :将索引i1~i2范围的值用a填充

flat() :嵌套数组转一维数组

遍历数组

let a = Array.of('a', 'b', 'c', 'd')
// for-in 取索引
for (let i in a) {
    console.log(a[i])  // a b c d
}

// for-of 取值
for (let v of a) {
    console.log(v)  // a b c d
}

// entries既取索引又取值
for (let [i, v] of a.entries()) {
    console.log(i, v) // 0 a  1 b  2 c  3 d
}

 数组中的map和reduce用法

 map类似于大数据中的map,一行输入,0行或1行或n行输出
map例子:
 
{
    // map 数据映射
    const json = [{ title: 'es6', status: 1 }, { title: 'react', status: 0 }, { title: 'webpack', status: 1 }, { title: 'vue', status: 1 }]
    let video = json.map(function (item) {
        // return {
        //     name: item.title,
        //     statusTxt: item.status ? '已上线' : '未上线'
        // }
        let obj = {}
        Object.assign(obj, item)
        obj.status = item.status ? '已上线' : '未上线'
        return obj
    })
    console.log('json', json)
    console.log('video', video)
}

 reduce类似于大数据中的reduce,一组输入,1行或n行输出,如何定义一组,由需求决定

参数解释:reduce(callback(acc, currValue, currIndex, array), initalValue) 

callback是一个回调函数,acc是每次回调的返回值,currValue是当前数组里进行回调的值,currIndex是currValue在数组中的位置,Array是回调的数组。 

initalValue是一个可选参数,如果填了,acc的初始值就是initialValue,如果不填acc的初始值是数组的第一项

reduce例子1:

// 统计字符次数
const letterList = 'abcadefrd'.split('')
const result = letterList.reduce(function (acc, cur) {
    acc[cur] ? acc[cur]++ : acc[cur] = 1
    return acc
}, {})
console.log(result)  //{ a: 2, b: 1, c: 1, d: 2, e: 1, f: 1, r: 1 }

reduce例子2:

//展开多层数组
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) // [1, '2nd', 2, 3, '3rd', 4, 5, '2nd', 6, 7 ]

 

*六、ES6和ES8对象新特性及新增方法

1、定义对象可用简写形式

{
    let name = '小明'
    let age = 18
    let es6Obj = {
        name,
        age,
        sayHello() {
            console.log('this is es6Obj')
        }
    }
    //等价于
    let es5Obj = {
        name: name,
        age: age,
        sayHello: function () {
            console.log('this is es5Obj')
        }
    }
}

还可以用表达式作为属性名

const hello = "Hello";
const obj = {
 [hello+"2"]:"world"
};
obj  //{Hello2: "world"}

 

2、拓展运算符(...)用于拷贝对象,合并对象,覆盖属性

{
    // 拷贝对象,浅拷贝
    const obj = { name: 'Nick', video: 'es6' }
    let videoObj = { ...obj }
    console.log(videoObj)

    //合并对象
    const initObj = { color: 'red' }
    let obj3 = { ...obj, ...initObj }
    console.log(obj3)

    // 自定义的属性在拓展运算符后面,则拓展运算符对象内部同名的属性将被覆盖掉
    let obj2 = { ...obj, name: 'Jack123' }
    console.log(obj2)
}

3、对象的遍历:Object.keys() Object.values(), Object.entries()

const json = {name: 'Nick', video: 'es6', date: 2019}
for (const key of Object.keys(json)) {
    console.log(key)   //name video date
}

for (const value of Object.values(json)) {
    console.log(value)  //Nick es6 2019
}

for (const [k, v] of Object.entries(json)) {
    console.log(k, v) // name Nick   video es6   date 2019
}

4、Object.assign(target, source_1, ···) 用于将源对象的所有可枚举属性复制到目标对象中,类似于对象合并

  • 如果目标对象和源对象有同名属性,或者多个源对象有同名属性,则后面的属性会覆盖前面的属性。
  • 如果该函数只有一个参数,当参数为对象时,直接返回该对象;当参数不是对象时,会先将参数转为对象然后返回。
//第一个参数是目标对象,后面的参数是源对象,后面会覆盖前面的同名属性
// assign 的属性拷贝是浅拷贝
let target = {a: 1};
let obj1 = {b: 2};
let obj2 = {c: 3};
let obj3 = {a: 999}
Object.assign(target, obj1, obj2, obj3);
console.log(target);  // { a: 999, b: 2, c: 3 }

//函数只有一个参数,参数转换为对象返回
Object.assign(3);         // Number {3}
typeof Object.assign(3);  // "object"

5、Object.is(value1, value2)  用来比较两个值是否严格相等,与(===)基本类似

与(===)的区别

//一是+0不等于-0
Object.is(+0,-0);  //false
+0 === -0  //true
//二是NaN等于本身
Object.is(NaN,NaN); //true
NaN === NaN  //false

 

*七、Map与WeakMap

map与object的区别

  • Object 的键只能是字符串或者 Symbols,Map 的键可以是任意值。
  • Map 中的键值是有序的(FIFO 原则),而添加到对象中的键则不是。
  • Map 的键值对个数可以从 size 属性获取,而 Object 的键值对个数只能手动计算
  • Object 都有自己的原型,原型链上的键名有可能和你自己在对象上的设置的键名产生冲突

Map

1、map对象的操作

// 初始化
let map = new Map([['name', 'Nick'], ['sex', 'male']])
map      //Map(2) { 'name' => 'Nick', 'sex' => 'male' }
map.size //2
// 添加元素,key可以是对象、方法、NaN
let map1 = new Map();
let obj = [1,2,3]
let funObj = function () {}
let nanObj = NaN
map1.set(obj, 'number').set('hobbies', ['swimming', 'running']).set(funObj, 'function').set(nanObj, 'not a number')
map1  //Map(1) { [ 1, 2, 3 ] => 'number',  'hobbies' => [ 'swimming', 'running' ], [Function: funObj] => 'function', NaN => 'not a number'}
// 查找元素
map1.get('hobbies')   //[ 'swimming', 'running' ]
map1.get([1,2,3])    //undefined   因为 obj !== [1,2,3]
map1.get(obj)    //'number'
// 元素是否存在
map1.has('hobbies')  //true
// 删除元素
map1.delete('hobbies')
// 清空
map1.clear()

map的转换、克隆、合并

// map 与 Array的相互转换
let kvArray = [["key1", "value1"], ["key2", "value2"]];
let myMap = new Map(kvArray);
let outArray = Array.from(myMap);
// map的克隆
var myMap1 = new Map([["key1", "value1"], ["key2", "value2"]]);
var myMap2 = new Map(myMap1);
console.log(myMap1 === myMap2);  //false
// map的合并 ,如果有重复的键值,则后面的会覆盖前面的
let first = new Map([[1, 'one'], [2, 'two'], [3, 'three'],]);
let second = new Map([[1, 'uno'], [2, 'dos']]);
let merged = new Map([...first, ...second]);

2、map对象的遍历:与object一样

// 将会显示两个 log。 一个是 "0 = zero" 另一个是 "1 = one"
for (const [key, value] of myMap) {
  console.log(key + " = " + value);
}

WeakMap

1、WeakMap与Map的区别

  • 只接受对象作为一个键名,不接受其他类型的数据作为键名
  • WeakMap键名是弱引用,不触发垃圾回收机制。换句话说,如果除WeakMap的key之外没有其他引用指向对象,对象就会被垃圾回收掉。
  • 只有set()、get()、has()和delete()四个方法,没有clear, 没有size, 无法遍历

2、WeakMap的使用场景

  借助WeakMap键名对象都是弱引用这一特点,WeakMap 可用于把 DOM 节点作为键名,当DOM节点被删除后,WeakMap内部的引用便会自动消失,可以有效的解决内存泄漏问题
  例如,我们将某节点对象作为WeakMap的键名,然后我们将存有点击次数的对象作为键值存放于WeakMap中,每当发生一次click事件,点击次数+1,一旦这个 DOM 节点删除,保存点击次数的对象就会自动消失,不存在内存泄漏风险

const wm1 = new WeakMap();
const key = {foo: 1};
wm1.set(key, 2);
wm1.get(key) // 2

const k1 = [1, 2, 3];
const k2 = [4, 5, 6];
const wm2 = new WeakMap([[k1, 'foo'], [k2, 'bar']]);
wm2.get(k2) // "bar"
wm2.delete(k1) //true
wm2.has(k1) //false

// 4. WeakMap只能接受对象作为键名,null除外
const map = new WeakMap();
map.set(1, 2)
// TypeError: 1 is not an object!
map.set(Symbol(), 2)
// TypeError: Invalid value used as weak map key
map.set(null, 2)
// TypeError: Invalid value used as weak map key

//5. WeakMap没有遍历方法,不能被遍历
const k1 = [1, 2, 3];
const k2 = [4, 5, 6];
const map = new WeakMap([[k1, 'foo'], [k2, 'bar']]);
map.size;//undefined
map.keys()//TypeError: map.keys is not a function

*八、Set与WeakSet

Set 对象存储的值总是唯一的,所以需要判断两个值是否恒等。有几个特殊值需要特殊对待:

  • +0 与 -0 在存储判断唯一性的时候是恒等的,所以不重复;
  • undefined 与 undefined 是恒等的,所以不重复;
  • NaN 与 NaN 是不恒等的,但是在 Set 中只能存一个,不重复。
1、数组去重
var mySet = new Set([1, 2, 3, 4, 4]);
[...mySet]; // [1, 2, 3, 4]
2、并集
var a = new Set([1, 2, 3]);
var b = new Set([4, 3, 2]);
var union = new Set([...a, ...b]); // {1, 2, 3, 4}
3、交集
var a = new Set([1, 2, 3]);
var b = new Set([4, 3, 2]);
var intersect = new Set([...a].filter(x => b.has(x))); // {2, 3}
4、差集
var a = new Set([1, 2, 3]);
var b = new Set([4, 3, 2]);
var difference = new Set([...a].filter(x => !b.has(x))); // {1}

其他的参考Map与WeakMap

 

*九、Map、Set与Array以及Object之间的区别以及转换

/**
 * Map、Set与Array以及Object之间的区别
 */
{
    let array = []
    let obj = {}
    let map = new Map()
    let set = new Set()
    const gooditem = { fruit: 'apple' }

    // 增加
    array.push(gooditem)
    obj['fruit'] = 'apple'
    map.set('fruit', 'apple')
    set.add(gooditem)
    console.log(array, obj, map, set)  // [{ fruit: 'apple' }] // { fruit: 'apple' } //Map(1) { 'fruit' => 'apple' } //Set(1) { { fruit: 'apple' } }

    // 查询
    const resultArray = array.includes(gooditem)
    const resultObj = 'fruit' in obj
    const resultMap = map.has('fruit')
    const resultSet = set.has(gooditem)
    console.log(resultArray, resultObj, resultMap, resultSet)   // true  true  true  true

    // 修改
    array.forEach(function (item) {
        item.fruit = item.fruit ? 'orange' : ''
    })
    obj['fruit'] = 'orange'
    map.set('fruit', 'orange')
    set.forEach(function (item) {
        item.fruit = item.fruit ? 'orange' : ''
    })
    console.log(array, obj, map, set)  //[{ fruit: 'orange' }] //{ fruit: 'orange' } //Map(1) { 'fruit' => 'orange' } //Set(1) { { fruit: 'orange' } }

    // 删除
    const index = array.findIndex(function (item) {
        return item.fruit
    })
    array.splice(index, 1)
    delete obj.fruit
    map.delete('fruit')
    set.delete(gooditem)
    console.log(array, obj, map, set)  //[] //{} //Map(0) {} //Set(0) {}
}

{
    // 类型转换 map和对象间的转换
    let obj = {
        name: 'Nick',
        hobbies: 'swimming'
    }
    console.log(Object.entries(obj))   //[ [ 'name', 'Nick' ], [ 'hobbies', 'swimming' ] ]
    let map = new Map(Object.entries(obj))
    console.log('map', map) //Map(2) { 'name' => 'Nick', 'hobbies' => 'swimming' }

    let obj2 = Object.fromEntries(map)
    console.log('obj', obj2)  //{ name: 'Nick', hobbies: 'swimming' }

    // 数组和set
    let array = [1, 2, 3, 4, 5]
    let set = new Set(array)
    console.log('set', set)   //Set(5) { 1, 2, 3, 4, 5 }
    let array2 = Array.from(set)
    console.log('array', array2)  //[ 1, 2, 3, 4, 5 ]
}

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

简介:介绍什么是Iterator及其作用和与for…of循环的关系
什么是Iterator?
Iterator(迭代器)是一种接口,目的是为了给不同的数据结构提供统一的遍历方式,任何数据结构如果实现了Iterator接口,就能够用for…of循环进行遍历。

什么结构默认实现了Iterator接口?
1、Array
2、String
3、Set
4、Map
5、函数的argument对象

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

例子:给object对象实现迭代器

// 应用场景,给object对象实现迭代器接口
const obj = {
    color: 'red',
    price: 18,
    size: 'small',
    [Symbol.iterator]() {
        let index = 0
        const values = Object.values(this)
        return {
            next() {
                if(index < values.length) {
                    return {
                        value: values[index ++],
                        done: false
                    }
                } else {
                    return {
                        done: true
                    }
                }
            }
        }
    }
}

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

// red
// 18
// small

 

*十一、Proxy与Reflect

Proxy
可以代理对象的一些操作,常用的操作如下:
{
    // Proxy, 代理的就是对象的一些操作

    let account = {
        id: 9923,
        name: 'admin',
        _private: 'test',
        phone: '13812345678',
        create_time: '2019'
    }

    let accountProxy = new Proxy(account, {
        // 拦截读取和设置的操作。需求:将手机号中间4位隐藏,并且把2019变成2020
        get: function (target, key) {
            switch (key) {
                case 'phone':
                    return target[key].substring(0, 3) + '****' + target[key].substring(7)
                case 'create_time':
                    return target[key].replace('2019', 2020)
                default:
                    return target[key]
            }
        },

        set: function (target, key, value) {
            //需求:不能设置id字段
            if (key === 'id') {
                return target[key]
            } else {
                return target[key] = value
            }
        },

        // 拦截key in obj
        has: function(target, key) {
            // 需求:打印出来
            if(key in target) {
                console.log(`${key}:`, target[key])
                return true
            } else {
                console.log('并无此属性')
                return false
            }
        },

        // 拦截delete
        deleteProperty: function(target, key) {
            //需求:私有属性不能删除
            if(key.indexOf('_') === 0) {
                console.warn('私有属性不能被删除')
                return false
            } else {
                delete target[key]
                return true
            }
        },

        // 拦截Object.keys()
        ownKeys(target) {
            //需求:过滤掉'id'和下划线
            return Object.keys(target).filter(function(item) {
                return item !== 'id' && item.indexOf('_') !== 0
            })
        }
    })

    console.log('拦截读取', accountProxy.phone, accountProxy.create_time)    // 138****5678 2020
    accountProxy.id = 1234
    accountProxy.name = 'guest'
    console.log('拦截设置', accountProxy.id, accountProxy.name)   // 9923 guest
    console.log('拦截in', 'sex' in accountProxy)  // false
    console.log('拦截删除', delete accountProxy['_private']) // false
    console.log('拦截Object.keys()',Object.keys(accountProxy))  //[ 'name', 'phone', 'create_time' ]
}

如要拦截其他操作,请看:https://www.runoob.com/w3cnote/es6-reflect-proxy.html

Reflect

动态获取或添加对象的属性

let obj = {
    name: 'Nick',
    age: '32',
    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')

 反射的其他知识,请看:https://www.runoob.com/w3cnote/es6-reflect-proxy.html

十二、ES6中函数的扩展

1、设置默认参数

function es6Print(x, y = 'world') {
    console.log('es6', x + y)
}
es6Print('hello', '')   //helloworld
2、不定参数
// rest, 不确定有多少个参数
function add(...rest) {  //rest直接就是数组,但argument不是数组
    let sum = 0
    for (let value of rest) {
        sum += value
    }
    console.log(sum)
    // Array.prototype.method.apply(arrgument)   //将argument变成数组
}
3、尾调用:
// 尾调用,提高性能,使用递归的时候改为尾调用执行
function step2(x) {
    console.log('尾调用', x)
}
function step1(x) {
    return step2(x)
}
4、箭头函数
什么时候用箭头函数,什么时候用普通函数
/**
 * 在JavaScript什么时候使用箭头函数??
 * 只要知道箭头函数做的是什么事就是可以了,大概如此:可以看出,箭头函数中的this是外部域的this
 * var _self = this; function fn () { console.log(_self) }
 * 以下四种情况用普通函数,其他情况用箭头函数
 * 1.定义对象方法,像fruit.sum()
 * 2.定义原型方法,比如Cat.prototype.sayHello=function(){}
 * 3.定义事件回调函数,xx.addListener('xx',function(){})
 * 4.定义构造函数
 */

十三、ES6中理解类的概念

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)
console.log(typeof p)  //object
console.log(typeof Person)   //function

ES6改造ES5实现类的方法。可以看出,ES6中的class实际上是function的语法糖

class Person {
    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)  //class Person { name: '小红', age: 17 }
console.log(typeof p)  //object
console.log(typeof Person)   //function

类的继承

// 类的继承,ES5中是用原型实现类继承
class Parent {
    constructor(name = 'Nick') {
        this.name = name
    }
}
class Child extends Parent {
    constructor(name = 'Jack') {
        // super要放在构造函数的最前面
        super(name)
        this.name = name
    }
}
console.log('继承', new Child())  //Child { name: 'Jack' }

构造器

class Person {
    constructor(name = 'Nick') {
        this.name = name
    }
    get fullName() {
        return this.name + '\xa0' + 'Liu'
    }
    set fullName(value) {
        this.name = value
    }
}
const p = new Person()
console.log('get', p.fullName)  // Nick Liu
p.fullName = 'Jack'
console.log('set', p.name)   // Jack

静态方法和静态属性

{
    // 定义静态方法
    class Person {
        constructor(name = 'Nick') {
            this.name = name
        }
        static sayHello(obj) {
            console.log('my name is ' + obj.name)
        }
    }

    const p = new Person('小花')
    Person.sayHello(p)   // my name is 小花
}

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

十四、模块化开发(import和export)

注意:要在package.json中加type=module,否则会报错

方式一:每个变量都export

模块a中每个变量都export

export let a = 3;    //导出变量

export function sayHello() {    //导出函数
    console.log('hello')
}

export class Test {   //导出类
    say() {
        console.log('test')
    }
}

再在模块b中import

import {a, sayHello, Test} from './chapter5-4.2.js'   //只引用某个
import * as test from './chapter5-4.2.js'       //全部引用

方式二:统一export

模块a中统一export

let a = 3;
function sayHello() {
    console.log('default', 'hello')
}

//推荐用这种,不会误操作
export default {
    a,
    sayHello
}

模块b中import

import mod from './chapter5-4.2.js'
console.log(mod.a)
mod.sayHello()

十五、Javascript中异步实现方式

1、回调函数

$.ajax({
    url:"",
    success: function (result) {
        $.ajax({
            url:"",
            success: function (result1) {
                
            }
        })
    }
})

2、setInterval和setTimeout

/**
 * 异步实现方式之:setInterval和setTimeout
 */
{
    console.log(1)
    setTimeout(() => {
        console.log(2)
    }, 0)
    console.log(3)
}

// 1
// 3
// 2

3、Promise

const p1 = new Promise((resolve, reject) => {
    if(success) {
        // 成功
        resolve(value)
    } else {
        // 失败
        reject(error)
    }
})

首先,Promise构造函数接受函数为参数,注意该函数是同步函数,Promise只要被new,function里面的逻辑会被立即执行;其次,该函数的两个参数分别是resolve和reject,这是两个回调函数,由js引擎提供.
Promise保存着异步操作的结果,进行中,成功或失败
resolve函数主要是实现状态从pending => resolved;如果状态变为resolved,value会传递给外部。
reject函数是将promise的状态从pending =>rejected·如果状态变为rejected,error会传递给外部。

示例:
-----------------

let promise = new Promise(function(resolve, reject) {
  console.log('Promise');
  resolve();
});

promise.then(function() {
  console.log('resolved.');
});

console.log('Hi!');

// Promise
// Hi!
// resolved

---------------------

resolve函数的参数除了可以是普通值外,还可以是另一个 Promise 实例,比如像下面这样:一个p2异步操作的结果是返回另一个p1异步操作
也就是说,p2的状态由p1决定,只有p1完成之后,p2才能完成,而且p1的结果会传递出去
以下两种写法是等价的
-------------------

//resolve参数是Promise对象
const p1 = new Promise((resolve, reject) => {
    resolve('7777777')
});
const p2 = new Promise((resolve, reject) => {
    resolve(p1);
})
p2.then(result => {
    console.log(result)
})

// 7777777

---------------------------

//then中返回Promise对象
const p1 = new Promise((resolve, reject) => {
    resolve('7777777')
});
const p2 = new Promise((resolve, reject) => {
    resolve();
})
p2.then(() => {
    return p1
}).then(result => {
    console.log(result)
})
// 7777777

------------------

//后面还可以.then无限连下去,只不过结果为undefined
p2.then(() => {
    return p1
}).then(result => {
    console.log(result)
}).then(result => {
    console.log(result)
})

// 7777777
// undefined

----------------------------------------

promise的异常处理

// 使用catch方法捕捉错误
function judgeNumber(num) {
    return new Promise((resolve, reject) => {
        if (typeof (num) === 'number') {
            resolve(num)
        } else {
            const err = new Error('error:类型不是number')
            reject(err)
        }
    })
}

judgeNumber('2')
    .then(num => console.log(num))
    .catch(err => console.log(err))

Promise.all :全部执行成功,才执行then

// Promise.all例子: 图片全部加载完成,页面才显示
const imgUrl1 = 'http://xd-video-pc-img.oss-cn-beijing.aliyuncs.com/xdclass_pro/video/1901/vue/vue.png'
const imgUrl2 = 'http://xd-video-pc-img.oss-cn-beijing.aliyuncs.com/xdclass_pro/video/1901/webpack/webpack.png'
const imgUrl3 = 'https://xd-video-pc-img.oss-cn-beijing.aliyuncs.com/xdclass_pro/video/2019_frontend/html_css/html.png'

function getImage(url) {
    return new Promise((resolve, reject) => {
        const img = document.createElement('img')
        img.src = url
        img.onload = () => resolve(img)
        img.onerror = (err) => reject(err)
    })
}


Promise.all([getImage(imgUrl1), getImage(imgUrl2), getImage(imgUrl3)]).then(images => {
    images.forEach(item => {
        document.body.appendChild(item)
    })
})

Promise.race:有一个执行成功,就执行then

// Promise.race例子:图片有一个加载完成就执行
Promise.race([getImage(imgUrl2), getImage(imgUrl1), getImage(imgUrl3)]).then(image => {
    document.body.appendChild(image)
})

4、Generator

先预习之前的迭代器相关知识。先看一个简单的例子:

// 生成器的定义要在function后面加上*
const say = function* () {
    yield 'a'
    yield 'b'
    yield 'c'
}
// 返回一个生成器对象
const fn = say();
// 调用生成器对象的next方法
console.log(fn.next())    //{ value: 'a', done: false }
console.log(fn.next())    //{ value: 'b', done: false }

应用场景一:用generator来实现object的迭代器

let obj = {
    a: 1,
    b: 2,
    c: 3
}

obj[Symbol.iterator] = function* () {
    for (const key of Object.keys(obj)) {
        yield obj[key]
    }
}

for (const value of obj) {
    console.log(value)
}
// 1
// 2
// 3

应用场景二:状态机,任何时候都只有一定数量种状态

const state = function* () {
    while(1) {
        yield 'success'
        yield 'fail'
        yield 'pending'
    }
}
const stateData = state()
console.log(stateData.next())  //success
console.log(stateData.next())  //fail
console.log(stateData.next())  //pending
console.log(stateData.next())  //success

应用场景三:长轮询,查询订单是否付款成功,前三次未付款,第四次付款成功

//用codeGen来模拟服务器的响应:未付款code=-1,已付款code=0
function* codeGen() {
    yield {code: -1}
    yield {code: -1}
    yield {code: -1}
    yield {code: 0}
}
const codeGen1 = codeGen();

// 长轮询,查询订单是否付款成功的功能,前三次未付款,第四次付款成功
function fn1() {
    return new Promise(resolve => {
        setTimeout( () => {
            console.log('查询中')
            resolve(codeGen1.next().value)
        },1000)
    })
}

const getStatus = function* () {
    yield fn1()
}

function autoGetStatus() {
    const gen = getStatus()
    const status = gen.next()
    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()   //第一个next用于启动生成器
    runAjax.next()

    // start
    // end
    // 异步任务执行结束
}

{
    const ajax = function* () {
        console.log('start')
        yield function (cb) {
            setTimeout(() => {
                console.log('异步任务结束')
                cb && cb()
            }, 1000)
        }
        console.log('end')
    }

    const runAjax = ajax()
    const first = runAjax.next()
    first.value(() => runAjax.next())

    // start
    // end
    // 异步任务执行结束
}

5、async

先看下面的异步代码,等待1s后,同时输出:任务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()

// 等待1s后,同时输出:任务1、任务2、任务3

如果我们想先输出任务1、隔1s再输出任务2、隔1s后再输出任务3,怎么实现??

async结合Promise使用,可以将异步变为同步,而且让代码看起来更加直观

下面代码还能进一步:await还可以用变量接收Promise的返回值
function login() {
    return new Promise<string>(resolve => {
        setTimeout(() => {
            resolve("token_KFDKLJKFLK");
        }, 1000);
    });
}

function getInfo(token: string) {
    return new Promise<string>(resolve => {
        setTimeout(() => {
            resolve(`${token}:  张三`);
        }, 1000);
    });
}

function getList(token: string) {
    return new Promise<string>(resolve => {
        setTimeout(() => {
            resolve(`${token}:  获取列表成功`);
        }, 1000);
    });
}

async function ini() {
    const token = await login();
    return {
        info: await getInfo(token),
        list: await getList(token)
    };
}

ini().then(res => {
    console.log(res);
});

/**
 * 输出结果:
 * { info: 'token_KFDKLJKFLK:  张三', list: 'token_KFDKLJKFLK:  获取列表成功' }
 */

 async和await踩坑:

//踩坑一: async中两个await会处于串行
//此处会让两个fetch处于串行
async function f() {
    const r1 = await fetch('http://...11');
    const r2 = await fetch('http://...22');
}

//改进:更高效的做法是用Promise.all
async function f1() {
    const promise1 = fetch('http://...11');
    const promise2 = fetch('http://...22');
    const [r1, r2] = await Promise.all([promise1, promise2]);
}


// 踩坑二: 不要在aync中使用forEach和map方法
// 这里的forEach会立刻返回,并不会等到所有异步操作都执行完毕
async function f2() {
    [1, 2, 3].forEach(async (i) => {
        await someAsyncOperation(i);
    });
}

f2();

//如果想让循环中所有异步操作一一完成后才继续执行,应该使用for循环
async function f3() {
    for (let i of [1, 2, 3]) {
        await someAsyncOperation(i);
    }
    console.log('done');
}

//如果想让循环中所有操作都并发执行,一种更炫酷的写法是使用for await,这里的for循环依然会等到所有异步操作都完成之后才继续向后执行
async function f4() {
    const promises = [
        someAsyncOperation(1),
        someAsyncOperation(2),
        someAsyncOperation(3)
    ];
    for await (let result of promises){

    }
}

// 踩坑三:不能在全局或普通函数中直接使用await关键字,await只能被用在异步函数中

async function f5() {
    await someAsyncOperation();
}
f5();

//或者更简洁的写法:
(async ()=>{
    await someAsyncOperation();
})();

 

 

 

posted on 2022-06-29 20:56  要一直走下去  阅读(72)  评论(0)    收藏  举报