告别石器时代#2:ES6新数据类型

Symbol

新的原始数据,表示独一无二的值 不能与其他数据或者自己运算

创建Symbol

javascript 复制代码
let s = Symbol()
let s2 = Symbol('chen')//描述字符串,相当于一个注释,不会影响唯一性
let s3 = Symbol('chen')//这是两个s1 != s3 他们只是描述相同
//获取描述
hd.description

作为Symbol对象

使用全局符号注册表,可以共享,重用, 传给全局符号注册表传的参数只能是字符串,就算不是也会变成字符串的

javascript 复制代码
let s4 = Symbol.for('chen')
let s5 = Symbol.for('chen')
//不会重复的创建Symbol对象,只会在全局创建一个Symbol对象
console.log(s4 === s5 )//true
//获取描述
cms = Symbol.for("hdcms")
console.log(Symbol.keyFor(cms))

基本 数据类型

USONB you ,so nb 一共七个

typescript 复制代码
undefine
string symbol
object
null number
boolean

💡 和其他数据类型不一样,Symbol不能new,避免创造符号包装对象,如果实在需要,就要使用 **`Object(mySymbol)`** 来创建

Symbol的使用

给对象添加同名属性,而且保持不覆盖原来的属性 在对象中使用Symbol方法的时候不能直接用Symbol(),因为这是动态的,是一个方法,而不是一个静态的名字,要用[Symbol()]

  • 法一
javascript 复制代码
let obj = {
        name:'chen',
        [Symbol('say')]: function() {},
        [Symbol('run')]: function() {}
}
  • 法二
javascript 复制代码
let game = {}
let methods = {
 up: Symbol(),
 down: Symbol()
}
game[methods.up] = function(){}
game[methods.down] = function(){}

Symbol可以实现私有属性保护

Object.getOwnPropertySymbols(hd)) {}Reflect.ownKeys(hd) { }

javascript 复制代码
let Example  = {
        name = 'czk',
        Symbol('zzj')
}
for (const val in example){}
for (const val of example){}//这两种办法都不能拿出Symbol
for(cosnt val of Object.getOwnPropertySymbols(Example))//这个只能拿出Symbol
for(cosnt val of Reflect.ownKeys(hd)//这个可以拿出全部
csharp 复制代码
let str = JOSN.stringtify(Example)//不会包含zzj

Symbol内置属性

内置Symbol值 描述
Symbol.iterator 一个返回一个对象默认迭代器的方法。被for...of使用。
Symbol.asyncIterator 一个返回对象默认的异步迭代器的方法。被for await of使用。
Symbol.match 一个用于对字符串进行匹配的方法,也用于确定一个对象是否可以作为正则表达式使用。被String.prototype.match()使用。
Symbol.replace 一个替换匹配字符串的子串的方法。被String.prototype.replace()使用。
Symbol.search 一个返回一个字符串中与正则表达式相匹配的索引的方法。被String.prototype.search()使用。
Symbol.split 一个在匹配正则表达式的索引处拆分一个字符串的方法.被String.prototype.split()使用。
Symbol.hasInstance 一个确定一个构造器对象识别的对象是否为它的实例的方法。被instanceof使用。
Symbol.isConcatSpreadable 一个布尔值,表明一个对象是否应该flattened为它的数组元素,是否可被打平。被Array.prototype.concat()使用。
Symbol.unscopables 拥有和继承属性名的一个对象的值被排除在与环境绑定的相关对象外。就是说能不能被with环境绑定,在这个对象中将某个属性值设为true,这个属性将不能被绑定
Symbol.species 一个用于创建派生对象的构造器函数。就是指定由一个Example对象派生(比如数组用了filter)出来的对象的类型,默认为源对象类型
Symbol.toPrimitive 一个将对象转化为基本数据类型的方法。很多方法都会将数据类型强制原始化,比如加减
Symbol.toStringTag 用于对象的默认描述的字符串值。被Object.prototype.toString()使用。内置类型已经指定了这个类型,但是自定义类要明确定义

这些属性都可以通过specificClass[Symbol.argname]来重写


Iterator

  • 任何实现Iterable接口的数据结构都能被实现iterator接口的结构'消费',迭代器是按需创建的一次性对象

  • 迭代器无需了解与其关联的可迭代对象的结构,只需要知道如何取得连续的值

    • 红宝书中说,这种概念是iterable和iterator的强大之处

默认 迭代器

  • ECMAScript中,可迭代协议规定必需暴露一个属性作为默认 迭代器 ,而且必须以Symbol.iterator 作为键,调用这个工厂函数返回一个迭代器

    • 如果原型链上的父类实现了iterator接口那么这个对象也实现了
    • 所以为什么迭代器是一个一次性对象但是我们却可以重复调用一个可迭代对象
javascript 复制代码
//迭代器
const str = 'abc'
const arr = [1, 2, 3]
const map = new Map().set(1, 2).set(2, 4)
const set = new Set().add('a').add('b')
// const dom
//调用迭代Symbol.iterator返回一个迭代器
console.log(str[Symbol.iterator])//ƒ 
Symbol.iterator
console.log(arr[Symbol.iterator])//ƒ values()
console.log(map[Symbol.iterator])//ƒ entries()
console.log(set[Symbol.iterator])//ƒ values()
  • 我们不用显式地调用这个函数来生成迭代器,以下是接受可迭代对象的原生语言特性

    • for-of
    • Array.form()
    • Promise.all()
    • Promise.race()
    • yield*
    • 数组赋值
    • 解构赋值符
    • 创建集合
    • 创建映射

迭代器 API

  • 使用next() 来在可迭代对象中迭代,返回值是iteratorResult ,是一个对象,包含{value:?,done:true|false}
  • done === true 即说明迭代器耗尽了
  • 迭代器在迭代期间不会和迭代对象快照绑定,迭代过程中迭代对象的变化也会反映在迭代器输出上

显式实现 迭代器

构造一个对象,这个对象要有一个Symbol.iterator 方法,调用这个方法返回一个迭代器,这迭代器就是一个对象,只要这个对象里面有next 方法就行

javascript 复制代码
//实现一个迭代器
class Myiterator {
    //实现迭代工厂函数接口
    
Symbol.iterator
 {
        return {
            next() {
                return { value: 'abc', done: false }
            }
        }
    }
}
const myit = new Myiterator()
const myiterator = myit
Symbol.iterator
console.log(myit
Symbol.iterator
)//调用工厂函数返回一个迭代器{next: ƒ}
console.log(myterator.next())//{value: 'abc', done: false}

调用原生 迭代器

javascript 复制代码
//调用默认的迭代器
let a = new Array(1, 2, 4)
const aiterator = a
Symbol.iterator
console.log(aiterator)//Array Iterator
console.log(aiterator.next())//{value: 1, done: false}

📌 迭代器本身也实现了迭代接口,什么意思呢? 注意这里迭代要用for of而不是for in

javascript 复制代码
//验证迭代器本身也实现了迭代接口
let arr1 = new Array(1, 2, 3)
let iter1 = arr1
Symbol.iterator
let iter2 = iter1
Symbol.iterator
console.log(iter1 === iter2)//ture
for (let item of arr1) {
    console.log(item)
}//1,2,3
for (let item of iter1) {
    console.log(item)
}//1,2,3

自定义 迭代器

vbnet 复制代码
const banji = {
name : 'calss11',
stus: [
        'xiaoing',
        'xiaohong',
        'lihua',
        'wuyifan'
        ]
}

要求遍历stus

scss 复制代码
banji.stus.forEach()

可以但是不符合面向对象的思想,不应该直接操作.stus

于是用iterator

下面这个例子还用到了闭包,这样才能用一个可迭代对象生成多个迭代器,否则其中的index执行一次之后回不到0

kotlin 复制代码
const banji = {
        name: 'calss11',
        stus: [
            'xiaoing',
            'xiaohong',
            'lihua',
            'wuyifan'
        ],
        
Symbol.iterator
 {
            let index = 0;
            return {
                next: () => {//这里要用箭头函数或者保存this
                    if (index < this.stus.length) {
                        const result = { value: this.stus[index], done: false }
                        index++//先保存再自加
                                                                                                return result
                    }
                    else { return { value: this.stus[index], done: true } }
                }
            }
        }
    }

于是就可以遍历了

scss 复制代码
for(let v of banji){
console.log(v)
}

提前退出

  • 在迭代器中,如果存在一个return 函数(和next是同级的),那么说明这个迭代器是可以提前结束的

  • 通过一些关键字来提前退出迭代(触发return 寒素)

    • break | continue | return | throw
  • 并非所有的迭代器都有return函数,比如array就没有,所以不能提前结束,即使break了迭代器也没有关闭, 如果继续调用同一个Symboliterator 就会继续上一次的迭代

kotlin 复制代码
const banji = {
        name: 'calss11',
        stus: [
            'xiaoing',
            'xiaohong',
            'lihua',
            'wuyifan'
        ],
        
Symbol.iterator
 {
            let index = 0;
            return {
                next: () => {//这里要用箭头函数或者保存this
                    if (index < this.stus.length) {
                        const result = { value: this.stus[index], done: false }
                        index++//先保存再自加
                                                                                                return result
                    }
                    else { return { value: this.stus[index], done: true } }
                }
,

                                                                
return: ()=>{
                                                                return {done:true}
                                                                }

            }
        }
    }

for in 和 for of

javascript 复制代码
const xiyou = ['a','b','c','d']
for (let v in xiyou) {
console.log(v)//0,1,2,3
}
for (let v of xiyou) {
console.log(v)//a,b,c,d
}

📌 for in 保存的是键名, for of 保存的是键值

for in

  • for in任意顺序 迭代一个对象除了Symbol以外的所有可枚举属性,包括继承的可枚举属性

  • for in 对于对象,遍历的是键,但是包括他的原型

    • 如果不考虑原型只关心本身建议用:

      • getOwnPropertyNames() | hasOwnProperty()不是继承的 | propertyIsEnumberable()可迭代的
  • for in 是为了遍历对象而构建的,不建议与数组一起使用

  • 数组可用for offorEach()

for of

  • 语句遍历可迭代对象定义的要迭代的数据

Generator

生成器实现了iterator 接口,其默认迭代器是自引用的,可以当做一个迭代器来使用

定义方法

  • function * generatorFn(){} 函数声明
  • let generatorFn = function* (){} 函数表达式
  • let foo = { * generatorFn (){}} 对象字面量方法的生成器函数
  • class Foo { *generatorFn(){}} 类实例方法
  • class Bar {static * generatorFn(){}} 类静态方法

实例方法

  • gen.next(value)

    • 返回值 {done :true | flase ,value : js}

    javascript 复制代码
      class Gen {
          * mygenerator() {
              yield 1
              yield 2
              yield 3
              yield 4
          }
      }//作为类实例方法创建的生成器
    
      const mg = new Gen()//第一次调用这个生成器
      let MG = mg.mygenerator() //启动生成器相当于启动这个生成器,没有执行里面的任何一条代码
      console.log(MG.next())
      console.log(MG.next())
      console.log(MG.next())
      console.log(MG.next())
      console.log(MG.next())
    • next() 传值

      • 调用next() 方法会得到一个value,这个value是yield后面的语句的求值结果
      • 得到这个结果,进行处理之后,可以再用next() 方法传回去
      • next(value) value会当作当前yield的整体返回值,实现向函数传参
  • gen.return(value) 通过return返回给定的值并结束生成器

    • 通过return 返回的值done = true
  • **gen.throw()** 向生成器抛出一个异常,这个异常可以被try...catch捕获

    • 在函数内部捕获的异常会导致生成器跳过对应的那个yield
javascript 复制代码
function * gen(){
//part1
        yield '一只有耳朵'//分割,让next在这里停住,这里是next的输出结果
//part2
        console.log(1)
        yield '一只没有耳朵'
//part3
        console.log(2)
//part4
}
vbnet 复制代码
let iterator = gen()
iterator.next()
iterator.next()
iterator.next()
iterator.next()
//调用方法特殊

yield return

  • 通过yield 退出的生成器会返回 done : false

    • yield 关键字必须直接出现在生成器中,不能嵌套
    • 通过yield 实现输入和输出:即使用next(value) 传参
    • 由于第一个next() 调用的时候不存在"当前yield",所以第一个next传参会被忽略
  • 💡 也就是说,生成器除了通过函数的形参来传参,还可以通过next()方法传参,并且通过`yield`和`next`实现了异步处理

  • 通过return 退出的生成器会返回done : true

yield*

就是加强版的yield*

  • yield*能够迭代一个可迭代对象
  • 不考虑返回值的话下面两个实例方法是等价的
javascript 复制代码
class yielding {
    * mygenerator() {
        yield* [1, 2, 3]
    }
    * mygenerator2() {
        for (let i of [1, 2, 3]) {
            yield i
        }
    }
}
  • yield*的返回值(当yield全部执行完之后生成器才会返回值,如果没有定义那么就是返回**undefine**)

    • 对于普通的迭代器来说,yield*返回undefine
javascript 复制代码
function* gen() {
    
console.log('final return :', yield * [1, 2, 3, 4, 5])// final return : undefined*
*
}
for (let i of gen()) {
    console.log(i)
}
javascript 复制代码
//对于生成器生成的迭代器而言
function* itCreating(n) {
    while (n--) {
        yield n
    }
    return 'abc'
}
function* gen() {
    
console.log(yield * itCreating(3))//abc*
*
}
for (let i of gen()) {
    console.log(i)
}

实现递归

生成器可以产生自身,所以可以用来实现的递归

scss 复制代码
//先看一个简单的递归是怎么样的
function Recursive(n) {
    if (n > 0) {
        // console.log(n)
        Recursive(n - 1)
        console.log(n)//写在后面就是最后打印
    }
}

Recursive(10)

//用yield* 实现一下
function* yieldecursive(n) {
    if (n > 0) {
        yield* yieldecursive(n - 1)
        yield n
    }
}
for (let i of yieldecursive(10)) {
    console.log(i)
}

// 把他展开看看

function* expendyieldcursive(n) {
    if (n > 0) {
        for (let t of expendyieldcursive(n - 1)) {
            yield t
        }
        yield n
    }
}
for (let i of expendyieldcursive(10)) {
    console.log(i)
}

实例

回调地狱:不断地在一个定时器里打开其他继续setTimeout导致代码缩进不断推进,阅读和维护困难

通过生成器函数解决这个问题:

scss 复制代码
<script>
        function one() {
            setTimeout(() => {
                console.log(111)
                iterator.next()
            }, 1000)
        }
        function two() {
            setTimeout(() => {
                console.log(222)
                iterator.next()
            }, 2000)
        }
        function three() {
            setTimeout(() => {
                console.log(333)
                iterator.next()
            }, 3000)
        }
        function* gen() {
            yield one()
            yield two()
            yield three()
        }
        let iterator = gen()
        iterator.next()
    </script>

Promise

Promise的设计导致了一种与同步分离的异步模式,用Promise抛出的错误用同步方法无法捕获

Promise对象

  • 三种状态:

    • pending 待定
    • fulfilled 兑现( resolved 解决 )
    • rejected 拒绝
    • 期约的状态是私有的,只能从内部操作
  • 创建了一个Promise对象 const myPromise = new Promise((resolve, reject) => resolve())

    • 这里的执行器的参数是Promise类传进来的,这样才能再我们的执行器函数中执行静态方法
    • Promise对象期待一个执行器(executor),其实就是一个回调函数
    • 执行器是同步进行的
  • 在回调函数中通过调用方法来改变Promise的状态

    • reject() → fulfilled
    • resolve() → rejected
    • 如果一直没有调用,那么promise对象就会一直处于 → pending 状态,为了防止pending一直持续下去,可以设置一个定时器,执行rejected()
    • 状态切换是不可逆的,已经落定settled之后再调用上面两个函数会被静默失败
  • Promise.resolve()

    • 接受一个参数,这个参数就是promise解决的期待值
    • 这个静态方法可以将任何值封装为一个期约
    • 如果传入的本来就是一个期约,那么就是空包装(也就是说Promise.resolve() 是幂等的
    • const myPromise = new Promise((resolve, reject) => resolve()) | const myResolve = Promise.resolve() 这两种写法其实得到的Promise对象完全一样
  • Promise.reject()

    • 和上面类似,接受的一个期待的错误理由

    • 如果传入的是一个期约,这个期约就是此期约拒绝的理由

    • 实例化一个拒绝的期约并抛出一个异步错误

      • 这个错误不能被catch,只能通过拒绝处理程序捕获
    • const myPromise = new Promise((resolve, reject) => reject()) | const myResolve = Promise.reject()

Promise基本使用

Thenable接口

  • 在ECMAScript中,异步结构中的所有对象中都有一个then() 方法

Promise.prototype.then(onResolved,onRejected) 原型方法

  • 接受两个方法 (两个函数)

    • 两个参数都是可选的,而且必须都是函数

    • 所以如果没有给then传入函数,成功的Promise对象给then方法传入的参数就会原样往后走

    • onResolved|onRejected 处理程序

      • 两个函数都接受一个参数,分别是Promise兑现的结果和Promise拒绝的理由
      • 如果在程序中抛出一个错误,或者返回一个Promise.reject()那么会返回一个拒绝的Promise
      • 如果函数返回一个值,那么它会被Promise(fulfilled)包装,就算返回一个Promise包装的类型,也和前面没有区别
      • 如果没有显式的返回值,那么Promise(fulfilled)默认包装一个undefine
      • 对于不是函数的,会默认静默处理为一个 (val) ⇒ val 就是返回原来的解决Promise或者拒绝Promise
ini 复制代码
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : 
(val) => val;

    onRejected =
      typeof onRejected === 'function'
        ? onRejected
        : 
(err) => {
            throw err;
          };

Promise.prototype.catch(onRejected) 原型方法

  • Promise.prototype.then(null,onRejected)的语法糖

Promise.prototype.finally(finallyFn) 原型方法

  • 对已经敲定状态的Promise对象执行

    • throw err 和 返回一个 Promise.reject() 会返回指定拒绝Promise
    • 如果返回一个状态未定的Promise :new Promise(()=>{}) 那么finally就会返回这个状态未定的Pormise
    • 其他情况都会不顾返回值直接返回原来的Promise
  • 对状态的Promise执行finally,无论如何都不会改变原Promise,直接返回


非重入期约方法

onResoled|onreject|catch()|finally()处理程序都只能异步执行

  • 当期约进入落定状态的时候,相关的状态处理程序只会排期,不会立刻执行
  • 但是执行器的函数是同步进行的
  • 等到同步线程进行完之后才会执行异步列表的任务

同步错误和异步错误

  • 异步错误用then/catch函数中的onReject()来处理,他的任务应该是抛出一个解决的期约

  • 同步错误用try/catch函数来自处理,他们的作用都是截获错误然后将其隔离,不影响正常逻辑运行

  • 同步错误的抛出会导致所有一下的语句停止运行

  • 异步错误的抛出不会影响同步代码的运行

  • 在执行器中,抛出reject/resolve函数之前我们任然可以用同步的try/catch来获取错误

    • 因为执行器是同步运行的

期约连锁和期约合成

期约连锁Promise chain

根据上面then()方法的返回Promise可行,可以给Promise返回的Promise

less 复制代码
p.then({}).then({})
  • 串行化异步任务

    • 让后面的异步任务等待前面的异步任务完成
    • 实现: 在then中返回一个异步函数(promise对象)
scss 复制代码
const myPromise = new Promise((resolve, reject) => resolve(1))
        myPromise.then(PromiseFactory).then(PromiseFactory).then(PromiseFactory)
        function PromiseFactory(val) {
            return new Promise((resolve, reject) => {
                setTimeout(() => {
                    console.log(++val)
                    resolve(val)
                }, (1000));
            })
        }

💡 容易写错的,then后面接受的是两个函数,而不是函数的调用

Promise.allSettle([]) 静态方法

始终是成功的,但是成功的值是里面每一个Promise的结果的值

  • 当所有的期约都被落定,返回一个成功期约
  • 返回值是所有期约的结果或原因的集合

Promise.all([]) 静态方法

都成功才是成功,有一个失败就返回失败的那个的值,有一个待定就是待定

  • 参数必须是一个可迭代对象,会自动Promise.resolve()包装成期约
  • 成功的返回值是成功期约的返回值组成的数组
  • 只有第一个错误期约的理由会被返回(断言)
  • 但是所有的错误期约都会被处理(不会放跑错误期约)

Promise.race([]) 静态方法

race就是赛跑,比赛谁快

  • 谁先落定就返回谁
  • 只会返回第一个错误的原因但是会处理所有拒绝期约

期约树 PromiseTree

以二叉树为例,Promise依赖上一个Primise产生新的Promise的结构可以用来构造树,每一个promise实例就是一个节点

javascript 复制代码
const A = Promise.resolve(console.log(1))
        const b = A.then((val) => {
            console.log(2)
        })
        const c = A.then((val) => {
            console.log(3)
        })
        const e = b.then((val) => {
            console.log(4)
        })
        const f = b.then((val) => {
            console.log(5)
        })
        const g = c.then((val) => {
            console.log(6)
        })
        const h = c.then((val) => {
            console.log(7)
        })
  • A就是根节点,在cd分别是左右节点,由于异步队列也是按顺序执行的,所以c执行完才会有的d执行,ef有分别是c的左节点和右节点,在c之后执行,因为先同步任务执行完才有异步任务队列的执行,所以这里节点的执行顺序只与加入的异步任务队列的时机有关
  • → 1,2,3,4,5,6,7

期约拓展 Promise Extend

中断调用链

我们不想要某个then之后的调用链继续下去,就给这个链返回一个pending状态的Promise

期约取消

封装一个类来返回一个能够被取消的期约,就是提前resolve,并停止里面的异步代码

**Promise.race() 实现**

伪代码:

  • 通过将我们要用的Promise和一个用来得到reject函数的Promise封装在一起,取消函数封装为**abort**,然后把我们的取消函数推进对象Promise中

    • 这一步是因为我们无法在外部改变Promise的状态,除非我们克隆一个reject压进去,然后调用我们压进Promise对象的**abort**方法
    • 然后在外部只需要调用abort方法就能直接结束这个期约
javascript 复制代码
//期约的取消
        let myPromise5 = new Promise((resolve, reject) => {
            setTimeout(() => {
                console.log('期约完成')//这个异步任务没有取消,但是我们已经不关心她的值了
                resolve()
            }, 5000);
        })
        function cancelablePromise(p1) {
            let abort
            const p2 = new Promise((resolve, reject) => { abort = reject })
            const p = Promise.race([p1, p2])
            p.abort = abort
            return p
        }
        myPromise5 = cancelablePromise(myPromise5)
        // setTimeout(myPromise5.abort, 0, "主动取消")
        document.querySelector('button').addEventListener('click', () => {
            myPromise5.abort('主动取消期约')
            console.log(myPromise5)
        })

超时自动取消版

javascript 复制代码
//超时自动取消版
        function PromiseTimeout(p1) {
            let p2 = new Promise((resolve, reject) => {
                setTimeout(reject, 2000, '期约超时取消')//2s超时
            })
            return Promise.race([p1, p2])
        }
        let myPromise6 = new Promise((resolve, reject) => {
            setTimeout(resolve, 5000, '期约成功')
        })
        myPromise6 = PromiseTimeout(myPromise6)

红宝书看不懂版/取消令牌法

cancel token

  • 封装一个cancelToken类,这个类把Promise的内部方法暴露给传入的取消函数

    • 取消函数接受的参数也是一个函数,这个函数一定要包含落定Promise的内置方法

    • 这个类中的Promise实际上是永远pendding的

    • 这个类有两个功能

      • 一给canelFn赋值,得到内部函数
      • 二是用内部Promise的then方法在得知取消操作被执行了关闭对象Promise的异步内容
  • 封装一个cancelablePromise函数,这个函数用来产生可以关闭的Promise对象

    • 返回值是一个Promise对象,Promise对象的执行器中包含了对取消令牌的调用,通过取消令牌的this.promise 的then方法提前结束此Promise对象
javascript 复制代码
//取消令牌 cancel token
        class CancleToken {
            constructor(cancelFn) {
                this.promise = new Promise((resolve, reject) => {
                    cancelFn(() => {
                        setTimeout(() => {
                            console.log('触发令牌Promise的取消')
                            resolve()
                        }, 0);
                    })
                })
            }
        }
        //可取消Promise
        function cancelablePromise_(delay) {//这里的参数看实际的Promise需要什么参数来用
            return new Promise((resolve, reject) => {
                let id = setTimeout(() => {
                    setTimeout(() => {
                        console.log('完成目标异步任务')
                    }, 0)
                }, delay)//这个表示实际要执行的异步任务
                //添加取消令牌,让他能够被取消
                const canceltoken = new CancleToken((cancelFn) => {
                    document.querySelector('#cancelbutton').addEventListener('click', cancelFn)
                })//取消令牌里面接受一个取消函数,此函数的参数还是一个函数,这个最里面这个函数就包含了Promise的内部方法
                canceltoken.promise.then(() => {
                    clearTimeout(id)
                    reject('目标函数提前落定')
                })//canceltoken.promise被落定了,现在我们还在目标Promise内部,可以执行内部函数
            })
        }
        document.querySelector('#addPromise').addEventListener('click', () => {
            console.log('生成异步任务')
            cancelablePromise_(5000)//5s事件执行目标Promise
        })

期约进度通知

伪代码

  • 目标:将Promise类继承并拓展,Promise的constructor本来接受一个executor 我们依然要使用这个executor,但是要给**executor新添加一个参数 notify ,这个参数和 resolve reject** 一样,是一个函数

    • resolve reject是写好在Promise父类方法中的原型方法,所以执行器可以直接调用(在这一步被形参命名),但是我们的notify是需要写的,用箭头函数,暴露参数和notify的程序内容
    • notify 是一个内部方法,是在执行器函数中被调用的,也是确定异步函数执行到什么程度发消息的函数
    • notify在内部调用的时候会接受一个表示状态输出值的参数status,这个值是写执行器函数的时候传入的
    • notify要实现的是依次调用**notifyFnList**里面的函数并传入得到的 status
    • 至于notifyFnList 里面的函数,使用原型方法 **notifySet**方法推入的
  • 我们要在外部调用推入方法到notifyFnList的时候要使用一个原型方法,所以还要封装一个像.then一样的原型方法,类比thencatch,这个原型方法notifySet需要返回一个Promise,而且可以连锁调用

    • **notifySet**接受的是输出的函数,表示添加此函数到消息输出函数
    • 其功能就是将函数推入到List中供执行器调用
javascript 复制代码
class NotablePromise extends Promise {
            constructor(executor) {
                //为了实现循环调用而设置的一个notifyList()
                const notifyList = [] //外部的notifySet就向这里推入新的notify函数就好,内部方法notify会逐个调用
                super((resolve, reject) => {
                    
return executor(
                        resolve, reject, (status) => {
                            //这个函数要在这里写好,不像resolve是Promise父类已经写在类中的可以直接调用
                            //循环调用notigy函数
                            notifyList.map((notifyFn) => notifyFn(status))//notifyFn函数接受一个参数当作输出内容
                        }//这个就是notify函数,无需命名,传入参数的时候就会命名
                    )//返回一个函数的调用
                })//向父类的executor函数传值,(父类的构造函数最后调用了exector(resolve,reject),父类会把传进去两个参数名封装好,但是我们还需要去封装一个新的函数,将executor做一下修改,我们构造函数最后也得返回一个executor的调用,但是要加上一个notifySet函数

                this.notifyList = notifyList
            }
            notifySet(notifyFn) {
                this.notifyList.push(notifyFn)
                return this//返回一个Promise
            }
        }
        const p = new NotablePromise(
            (resolve, reject, notify) => {
                let x = 0
                let id = setInterval(() => {
                    x++
                    
**notify(**
**x: ${x}**
)//要输出的东西

                    if (x >= 10) {
                        clearInterval(id)
                        resolve(x)
                    }
                }, 100);
            }
        )//接受一个executor,executor里面包含三个函数
        p**.notifySet((x) => {
            console.log(x)
        }).notifySet((x) => {
            console.log('data:', x)
        }).**then((value) => {
            console.log('finished:', value)
        })

📌 最难理解的是给父类传入executor然后返回一个新的executor的执行那一段 执行器是写在`constructor`中的所以构造实例的时候就会执行,**而我们添加的函数就是在我们实例化的时候被调用**,在**形参处写的是箭头函数**但是调用的时候任然可以给个名字然后调用


Promise 封装ajax

javascript 复制代码
const btn = document.querySelector("button")
        const p = new Promise((resolve, reject) => {
            btn.addEventListener("click", function () {
                //创建对象
                const xhr = new XMLHttpRequest()
                //打开通道
                xhr.open("GET", "
autolinkhttp://127.0.0.1:5500/server1autolink
")
                //发送请求
                xhr.send()
                //绑定回调函数
                xhr.onreadystatechange = function () {
                    if (xhr.readyState === 4) {
                        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 + "出问题了")
        })
javascript 复制代码
//Promise写法
//把data返回resolve --> value
const p = new Promise((resolve, reject) => {
    fs.readFile("ajax/a", (err, data) => {
        resolve(data)
    })
})
p.then(value => {//then方法里继续创建promise对象然后合并,
    return new Promise((resolve, reject) => {
        fs.readFile("ajax/b", (err, data) => {
            resolve([value, data])
            //再次调用成功函数,将value就是第一个返回值和data第二个返回值放到同一个数组里
            //value+date --> value给下一个then方法的value接收去
        })
    })
}).then(value => {
    return new Promise((resolve, reject) => {
        fs.readFile("ajax/c", (err, data) => {
            resolve([...value, data])//把第三个返回值加到数组里面传给下一个value
            //也可以写作
            value.push(data)
            resolve(value)
        })
    })
}).then(value => {
    console.log(value.join('\r\n'))
})

应用

在js中引入js (node.js)

ini 复制代码
const fs = require("fs")

注意,浏览器是不支持fs这个模块的,(出于安全性)要用浏览器的话要使用File API

javascript 复制代码
//要实现需求,在一个文件中读取东西,根据里面的内容来读取第二个文件
//嵌套写法
const fs = require("fs")
const { resolve } = require("path")
fs.readFile("ajax/a", (err, data1) => {
    fs.readFile("ajax/b", (err, data2) => {
        fs.readFile("ajax/c", (err, data3) => {
            let result = data1 + data2 + data3
            console.log(result)
        })
    })
})

async await

语法

是一个关键字,返回的是一个promise对象 让我们用同步方式执行异步的函数

async

  • 返回一个值:包装成Promise.resolve
  • 返回一个期约: 直接返回这个期约
  • 返回一个有thenable接口的对象,直接返回这个对象

await

必须放在async函数中,注意不能嵌套函数 异步函数中其实真正有用的是await,如果没有await其实和同步函数一样(除了返回值会被包装成Promise)

  • 如果右侧是Promise对象(有thenable接口的对象)就等待Promise然后求值
  • 如果右侧是不是实现了thenable接口的对象,就当作已经完成的期约
  • 右侧是一个抛出错误的同步函数,返回一个拒绝的Promise
  • 就算右侧是一个立即可用的值,也会被异步求值
javascript 复制代码
const p = new Promise((resolve,reject) => {resolve('成功')}
async function fn() {
        let result = await p
        console.log(result)//resolve
}
fn()
javascript 复制代码
const p = new Promise((resolve,reject) => {resolve('成功')}
async function fn() {
        try{

        let result = await p
        console.log(result)//resolve
        }catch(e){
console.log(e)
}
}
fn()

异步函数策略

实现 sleep()

用一个异步函数封装一个像java中 一样的非阻塞暂停函数

javascript 复制代码
//sleep
        function sleep(delay) {
            return new Promise((resolve, reject) => {
                setTimeout(resolve, delay)
            })
        }
        async function testingSleep() {
            await sleep(1000)
            console.log('delay finished')
        }
        testingSleep()

平行执行

如果执行的顺序不是那么重要,可以用平行执行来节约时间

scss 复制代码
async function Promiscreat(number) {
            const delay = Math.random() * 1000
            return new Promise((resolve, reject) => {
                setTimeout(() => {
                    console.log(number)
                    resolve(number)
                }, delay);
            })
        }
        async function timeSaving() {
            await Promiscreat(1)
            await Promiscreat(2)
            await Promiscreat(3)
            await Promiscreat(4)
        }
        timeSaving()

⚠️ 这样虽然会按照顺序输出,但是会等待上一个Promise完成才执行下一个,会浪费时间,如果执行的顺序不是那么重要,那么可以让他们平行执行

typescript 复制代码
async function Promiscreat(number) {
            const delay = Math.random() * 1000
            return new Promise((resolve, reject) => {
                setTimeout(() => {
                    console.log(number)
                    resolve(number)
                }, delay);
            })
        }
        async function timeSaving() {
            const p1 = Promiscreat(1)
            const p2 = Promiscreat(2)
            const p3 = Promiscreat(3)
            const p4 = Promiscreat(4)
            await p1
            await p2
            await p3
            await p4
        }
        timeSaving()
  • 这里的输出会不是按顺序的,但是await是按顺序得到值
javascript 复制代码
async function timeSaving() {
            const p1 = Promiscreat(1)
            const p2 = Promiscreat(2)
            const p3 = Promiscreat(3)
            const p4 = Promiscreat(4)
            console.log(await p1)
            console.log(await p2)
            console.log(await p3)
            console.log(await p4)
        }

实现串行执行期约

利用了async会将右侧的东西包装为期约的特性,让右侧得到值之后才能往下走

javascript 复制代码
function one(x) {
            console.log(x)
            return x + ' one'
        }
        function two(x) {
            console.log(x)
            return x + ' two'
        }
        function three(x) {
            console.log(x)
            return x + ' three'
        }
        async function sum(x) {
            for (let fun of [one, two, three]) {
                x = await fun(x)
            }
            return x
        }
        sum('zero').then((value) => {
            console.log(value)
        })

⚠️ 时刻注意,异步函数返回的是一个`Promise`,我们用`then`方法处理它的返回值

栈追踪和 内存 管理

scss 复制代码
function PromiseExecutor(resolve, reject) {
            reject('bar')
        }
        function PromiseUsing() {
            new Promise(PromiseExecutor)
        }
  • 这是用的Promise

    • 可以看到,JS尽可能完成的保留了调用栈的信息
    • 这样会很完整但是不可避免地会占用更多的内存空间
  • 这是用了异步函数

    • 如果用异步函数,这里已经完成的PromiseExecutor就不会被记录
    • 在在重视性能的开发中可以用
javascript 复制代码
async function PromiseUsing2() {
            await new Promise(PromiseExecutor)
        }
        PromiseUsing2()

应用

用await和async封装函数读取

javascript 复制代码
const fs = require('fs')
function reada() {
    return new Promise((resolve, reject) => {
        fs.readFile('./ajax/a', (err, data) => {
            if (err) reject(err)
            else resolve(data)
        })
    })//封装读取文件的函数,返回值是一个Promise对象
}
function readb() {
    return new Promise((resolve, reject) => {
        fs.readFile('./ajax/b', (err, data) => {
            if (err) reject(err)
            else resolve(data)
        })
    })//封装读取文件的函数,返回值是一个Promise对象
}
function readc() {
    return new Promise((resolve, reject) => {
        fs.readFile('./ajax/c', (err, data) => {
            if (err) reject(err)
            else resolve(data)
        })
    })//封装读取文件的函数,返回值是一个Promise对象
}
async function reading() {
    let a = await reada()
    let b = await readb()
    let c = await readc()
    console.log(a + b + c)
}
                reading() 

用async和await封装 AJAX

javascript 复制代码
        function sendAJAX(url) {
            return new Promise((resolve, reject) => {
                const x = new XMLHttpRequest()
                x.open("GET", url)
                x.send()
                x.onreadystatechange = function () {
                    if (x.readyState === 4) {
                        if (x.status >= 200 && x.status < 300) {
                            resolve(x.response)
                        } else {
                            reject(x.status)
                        }
                    }
                }
            })
        }
        // sendAJAX("<http://localhost:5500/server>").then(value => {
        //     console.log(value)
        // }, reason => { })
        async function main() {
            let result = await sendAJAX("<http://localhost:5500/server>")
            console.log(result)
        }
        main()

Set

类似一个数组,但是值唯一就是数学里集合的概念,是一个对象,所以有方法和属性

声明

javascript 复制代码
let s = new Set()
let s2 = new Set(['1','1','2','3'])//自动去重

方法

arduino 复制代码
setObj.size  //大小
setObj.add  //添加
setObj.delete  //删除
setObj.has //查找
setObj.clear //清空
javascript 复制代码
for (let v of s2) {
console.log(v);
}

去重

javascript 复制代码
let result = [... new Set(arr)]//arr是一个数组

交集

javascript 复制代码
let arr = [1,2,3,4,5,6,7,8]
let arr2 = [4,5,6,5,6]
let arr1 = [... new Set(arr)]//arr是一个数组
let result = arr.filter(item => {
        let s2 = new Set(arr2)//去重
        return s2.has(item)
})
javascript 复制代码
let result = [... new Set(arr)].filter(item => new Set(arr2).has(item))

并集

javascript 复制代码
let union = [...new Set([...arr1,...arr2])]

差集

注意是求谁的差集

javascript 复制代码
let diff = [... new Set(arr)].filter(item => !new Set(arr2).has(item))

weakSet

weakset中的值只能是对象,原理和weakMap是一样的

  • 使用weakset和使用weakmap很相似,但是weakset没那么有用,就是用来打标签

  • 例子,要给一个dom元素打上禁用的标签,查询她是否在Disable这个weakset中,如果dom元素不存在了,那么他在weakset中的值就会被回收,因为weakset中的值是弱引用


Map

键值存储机制,与Object有一些区别

区别

  • Object的键只能是数值,字符串和符号做键

    • 而Map可以用任何数据
  • Map会维护键值对的插入顺序,而Object不会

  • 性能对比

    • 空间占用:相同的空间,Map可以多存储50%
    • 插入速度:Map的插入速度稍快
    • 查找速度:Object可以在内存中使用更高效的布局,但是Map不能
    • 删除性能: Map更好

创建一个Map

  • Map接受一个可迭代对象,但是迭代对象必须有键值对结构

    • 可以传入一个嵌套数组,甚至可以直接传入一个自定义迭代器
  • API

    • get() 获得键对应的值
    • has() 判断键是否再
    • delete() 删除对应键值对
    • clear() 删除所有键值对
  • 当作键或者值的对象内容和属性修改不影响键值对的对应关系

顺序和迭代

  • map可以提供一个迭代器(Iterator)

    • 通过Symbol.interator属性或者entries()方法获取这个迭代器

    • m[Symbol.interator] === m.entries()

    • m.keys() | m.value() 分别获取键和值

    • 迭代的过程中在过程中修改的key和value都不会影响map本身,因为js是按值传递

      • 如果是修改作为key或value的对象的属性值,是有效的,这与按值传递不冲突

Weak Map

弱映射中弱是关于javascript的垃圾回收机制

  • 创建一个弱映射: const wm = new WeapMap()
  • 弱映射的键只能是对象或者是继承于Object的类型,否则报错
  • 📌 解释,weak Map的出现是为了更好的让没被引用的对象被回收,如果让原始值作为键的话,创建键值对的时候就会拷贝一份,那么这个键就和原来的那个原始值失去了联系,就算原来的原始值已经没用了,里面这个键仍然无法被回收。对于map和object类型,由于没有自动回收,要程序员手动删除键值对,(object中用null,undefine来伪装删除了)所以无所谓,但是对于weak map他的作用而言就不行了。而如果键是对象,那么传递的就是引用值,由于这是个 弱键 不计入垃圾回收标记,不算一个引用,所以当外部不再引用这个对象,那么这个键值对就会被回收,达到自动回收的目的。
  • 初始类型可以先包装一下当作对象再来用
  • API和map类似,是其子集 ,没有clear()方法

弱键

  • 不是指值弱弱拿着,只要键存在,那么键值对就会一直存在,并且当作对值的引用,不会被当垃圾回收
  • 但是当外部的对这个键所对应的对象失去所有引用的时候,这个键值对就会被回收,因为弱键就是弱在此键值对不算作对此键的引用。(或者说是一个弱引用,可以绕开垃圾回收机制)

不可迭代键

  • 因为任何时候都可能被销毁
  • 因此没有**clear()**方法
  • 所以无法在不知道键的情况下拿到值

Weak Map防止内存泄露

好好利用键失去引用的时候就会直接删除这个键值对的特性

  • 用来绑定dom元素,当dom元素被删除的时候,以dom为键的键值对就会被回收,防止内存泄露
  • 用来保存事件监听的函数,当监听的对象消失的时候,这个函数也没必要有了
dart 复制代码
//prevent memory leak
const wm = new WeakMap()
const dom = document.querySelector('example')
wm.set(dom, 'something a function or other')
//用WeakMap来保存事件监听的callback函数
const listener = new WeakMap()
listener.set(dom, () => { })
dom.addEventlistener('click', listener.get(dom))

Class

class 类

通过class关键字来构建类,其实现的功能和构造函数差不多, 视为一个语法糖,实际上他是一个特殊的函数instanceof function => true

声明和使用

  • 和构造函数一样,类名约定大写开头来区分普通函数/实例和类

  • 类的构成包括 (都是非必须的)

    • 构造函数方法 constructor() 固定名字
    • 获取函数get()
    • 设置函数set()
    • 静态类方法,实例无法访问的 static function(){}
  • 声明方法

    • 类声明class Person()
    • 类表达式const Animal = class()
    • 类和函数的区别: 函数的声明可以提升但是类的声明是不能的,函数受限于函数 作用域 而类限制于 作用域 类和构造函数的区别: 构造函数不用new 实例化只会用window (全局)当作this,类不用new实例化会报错,包括类中的constructor当作构造函数来使用的时候也要加new
    • 声明同时实例化
    kotlin 复制代码
      const czk = new class Person {
       constructor (name){
      this.name = name}
      }('czk')
    javascript 复制代码
      class Phone {
          constructor(brand, price)//名字固定,但是不写也是合法的,只是没有初始化的属性而已
          {
              this.brand = brand
              this.price = price
          }
          call() {
              console.log("打电话啊")
          }
      }
      let OnePlus = new Phone('one+', 2999)
      OnePlus.call()
      console.log(OnePlus.price)
    • 注意constructor是固定名字的,每次实例化就会执行这个函数,相当于python中的init

类的实例化过程

  • 在内存中创建对象

  • 对象内部的[[prototype]]指针被赋值

  • 函数内部的this被指向这个新对象

  • 执行构造函数内部的代码,给新对象添加属性

  • 构造函数返回非空对象,那就返回这个对象;否则返回刚新创建的对象

    • 类构造函数默认返回this,这就是实例化的对象
    • 如果返回的不是this,会导致instanceof 搜查不出来

如果let p1 = new Person() 那么: p1 instanceof Person.constructor === false 如果let p1 = new Person.constructor() 那么: p1 instanceof Person === false

原型方法

  • 定义在constructor中的函数reading和定义在类块中的seeing函数都可以直接通过实例化来访问

  • 其中定义在类块 中的函数会挂载在Person的prototype

    • 定义在constructor上的函数是挂载在实例上的
    • (属性不论写在哪里都是挂载在实例上的) 用红宝书的话说就是:所有的成员都不会在原型上共享,定义在constructor外面的函数可以被共享
    javascript 复制代码
      class Testing {
                  constructor(a, b) {
                      this.a = a
                      this.b = b//属性都是定义在类上
                      this.ConstructorFn = function () {
                          console.log('This is a function defined in constructor')
                      }//定义在类上
                  }
                  solidArg = 'solidArg'
                  solidFn() {//定义在原型上
                      console.log('this is a solidFn defined in class body')
                  }
                  static staticArg = 'staticArg'
                  static staticFn() {
                      console.log('this is a static function')
                  }
              }
    ini 复制代码
      console.log(test.ConstructorFn === test2.ConstructorFn)//false
      console.log(test.solidFn === test2.solidFn)//true
  • 放在constructor中的属性可以被 **super(element1,element2)** 继承

    javascript 复制代码
      class Person {
          constructor(name) {
              this.name = name;
              this.reading = () => { console.log('instance') }
          }
          reading() { console.log('prototype') }
          seeing() { console.log('seeing') }
      }
      let czk = new Person('Chenzikai')
      czk.reading()// instance
      Person.prototype.reading()// prototype
      czk.seeing()// seeing
  • 红宝书中说方法可以定义在构造函数和类块中但是不能在类快中给原型添加原始值或者对象作为成员数据,现在已经可以了

    arduino 复制代码
      class Animal {
          name = 'dog'
      }
      const dog = new Animal()
      console.log(dog.name)//dog

获取和设置方法

  • set functionname() | get functionname()

静态成员 static

静态成员直接定义在类本身上,而不是像其他方法定义在 prototype 上 静态成员只能定义在类块中,不能定义在constuctor上

  • 实例对象中的属性是和构造对象中的属性不相通的,和原型是相通的
javascript 复制代码
function Phone(){}
Phone.name = 'a' //这个就相当于静态属性,实例不能访问,是里访问的是prototype上的
Phone.change = function() {}
let nokia = new Phone()
console.log (nokia.name)//undefine
Phone.prototype.size = '5.5' 
nokia.size//5.5
  • class : 对应的静态属性和方法在es6+中这样写
javascript 复制代码
class Phone {
    constructor(brand, price)//名字固定
    {
        this.brand = brand
        this.price = price
    }
        static name = 'nokia'//实例对象不能访问
  static function() {}
}

红宝书中说每个类只能有一个静态对象,现在没有这个限制了

静态对象非常适合用来做实例工厂,就是返回实例的一个类,但是实例类不需要再拥有这个工厂能力

继承

构造函数中的 继承

javascript 复制代码
function Phone(brand, price) {
    this.brand = brand
    this.price = price
}
Phone.prototype.call = function () {
    console.log('我能打电话')
}
function smartPhone(brand, price, color, size) {
    Phone.call(this, brand, price)//改变this的指向,指向外面这个this
    this.color = color
    this.size = size
}
smartPhone.prototype = new Phone()
smartPhone.prototype.constructor = smartPhone//注意这里要指回去
smartPhone.prototype.playGame = function() {
    console.log ('我还可以打游戏')
}//给子类指定方法

类中的继承

类不仅可以继承 类还可以继承构造函数 派生类可以通过原型链访问到类和原型上定义的方法和成员

javascript 复制代码
//class实现
class Phone {
    constructor(brand, price) {
        this.brand = brand
        this.price = price
    }
    call() {
        console.log('我可以打电话')
    }
}
class smartPhone extends Phone {//这里就已经可以用父类的方法了
    constructor(brand, price, color, size) {
        super(brand, price)//继承父类的
        this.color = color
        this.size = size
    }
    photo() {
        console.log('可以拍照')
    }
    call() {
        console.log('重写父类的call方法')
    }
}
const sp = new smartPhone('xiaomi', 1999, 'red', '5.5')
sp.call()

不可以在调用super之前引用this(当然是在contructor,因为contructor定义了此实例的this)在类块中是不用写this的,直接name = ???就行

super

super是有点特殊的语法

  • 基本语法

  • super([arguments]) // 调用父类的构造函数

  • super.propertyOnParent

  • super[expression]

    • 作为函数调用super(...args)

      • 在派生类的构造函数中contructor 中可以用函数形式
      • 调用 父类的构造函数并绑定**公共字段,**其中super的参数就是给父类传参
      • 然后派生类可以进一步修改this
    • 在子类的构造函数中必须出现 **super()** 或者 返回一个对象 在子类中super() 调用父类构造函数,然后才能出现this

    • 作为属性查询super.prop|super[expr]

    • 不能单独调用console.log(super)

  • super() 放在**子类的constructor()**中,用来调用父类的构造函数,调用父类构造函数之后才能再对子类的this做修改和添加

  • super.FatherPrototypeFn 用于在子类的声明字段调用父类的方法,包括静态方法和定义在prototype上的方法

    javascript 复制代码
      class Father {
          constructor(arg1, arg2) {
              this.arg1 = arg1
              this.arg2 = arg2
          }
          static solid = 'solid'
          
      static staticfun() {
              return 'this is a solid function'
          }
          protoFn() {
              return "this is a function define in Father prototype"
          }
    
      }
      class Son extends Father {
          constructor(arg1, arg2, arg3) {
              super(arg1, arg2)
              this.arg3 = arg3
          }
          //如果我要在类中定义一个和父类的静态方法有关的函数就得用super
          static sonfunc() {
              return 
      super.staticfun()
       + ' in son class'
              // console.log('sonfunc')
          }
          sonProtoFn() {
              return 
      super.protoFn()
       + ", however it is now on Son's prototype"
          }
      }
  • 下面是一种错误用法

    scala 复制代码
      class Son extends Father {
          constructor(arg1, arg2, arg3) {
              super(arg1, arg2)
              this.arg3 = arg3
              this.itselfFn = () => {
                  return super.itselfFn() + arg3//这个函数是定义在构造函数上的,也就是是挂载在实例上而不是在原型上的,所以无法用super调用
              }
          }
          //如果我要在类中定义一个和父类的静态方法有关的函数就得用super
          static sonfunc() {
              return super.staticfun() + ' in son class'
              // console.log('sonfunc')
          }
          sonProtoFn() {
              return super.protoFn() + ", however it is now on Son's prototype"
          }
      }
    • 定义在父类的构造函数上的是要挂载在实例上的函数,而不是在原型上,所以是不能够用super.FatherprototypeFn()调用的

get 和 set

get是读取的时候触发的函数,set的时候触发的函数,一般用于判断赋值是否合法

javascript 复制代码
class Phone () {
        get price() {
                console.log('属性被修改')//读取这个属性的时候会执行这个函数
                return 'iloveyou'//返回值就是修改值

        }
        set price(newVal)//一定要有一个参数
                {
                console.log('价格属性被修改')

        }
}
let s = new Phone ()
s.price = 'free'//会执行set那一行
console.log(s.price)//会执行get那个函数

抽象基类

定义一个只供继承的类,本身不能被实例 new.target 会保存所有通过new关键字调用的函数和类

  • 在抽象基类的contructor函数中添加
scala 复制代码
class basicClass {
    constructor (){
        console.log(new.target)
        
if (basicClass === new.target){
            throw new Error('basicClass can not be directly instantiated')
        }

    }
}
class Son extends basicClass{
    constructor (name){
        super()
        this.name = name
    }
}
let basic = new basicClass ()
let son = new Son()
  • 以此类推,我们可以contructor要求子类必须声明某个方法
javascript 复制代码
class basicClass {
    constructor() {
        console.log(new.target)
        if (basicClass === new.target) {
            throw new Error('basicClass can not be directly instantiated')
        }
        
if (!this.nessesary) {
            throw new Error('the function nessesary must be define')
        }

    }
}
class Son extends basicClass {
    constructor(name) {
        super()
        this.name = name
    }
    nessesary() {
        console.log("sessesary was defined")
    }
}

继承 内置类型

某些类具有内置方法,其中一些方法可能会返回新的实例 这个实例与调用这个方法的类默认是同一个类型,如果想要修改这种行为,覆盖 Symbol.species 即可

scala 复制代码
//继承内置类型
class superArray extends Array {
    //这种Array添加了一个新方法,叫做superArray
    superman() {
        console.log(this, "above is a super array")
    }
}
const a1 = new superArray("tony", 1, 2, 3, 4, 5)
a1.superman()
const a2 = a1.filter((val) => {
    return val >= 3
})
console.log(a2 instanceof superArray)
  • 这里这个a2还是superArray,说明a2还是可以继续用superman方法
  • 如果我们希望只有a1是superArray,用a1造出来的实例是普通Array就要覆盖Symbol.species
scala 复制代码
class superArray extends Array {
    //这种Array添加了一个新方法,叫做superArray
    
**static get **
**Symbol.species**
 {
        return Array
    }

    superman() {
        console.log(this, "above is a super array")
    }
}
const a1 = new superArray("tony", 1, 2, 3, 4, 5)
a1.superman()
const a2 = a1.filter((val) => {
    return val >= 3
})

console.log(a2 instanceof superArray)//false
a2.superman()//TypeError: a2.superman is not a function

类混入

将不同类的行为混合到同一个类

  • 如果只需要不同类的属性,只需要用Object.assign复制一下

    • Object.assign()静态方法将一个或者多个源对象中所有可枚举类型和自有属性复制到目标对象,返回一个目标对象
    • 本质上就是在源对象上使用[[get]] 在目标对象使用 [[set]]
    • 相同键名将被覆盖
    • 不可枚举属性和原型链上的属性不会被复制
    • 复制的时候基本类型会被封装成对象,所以undefine和null会被忽略
    • 还有更多深入了解,用到的时候再学
  • 问题是我们现在要混合的不止是类的属性,还有方法行为,这就要自己实现混合表达式

提要,class a extends b 这个b只要是任何可以求值为构造函数的表达式都行

  • 方法一:可嵌套函数
scala 复制代码
//混合类,将多个类的行为混合到一个超类中
//方法一,嵌套继承
//目标类
class vehical { }
//需要三个函数 每一个函数负责混合一个类
function Amixin(SuperClass) {
    return class extends SuperClass {
        A() {
            console.log('a')
        }
    }
}
function Bmixin(SuperClass) {
    return class extends SuperClass {
        B() {
            console.log('b')
        }
    }
}
function Cmixin(SuperClass) {
    return class extends SuperClass {
        C() {
            console.log('c')
        }
    }
}
let SuperClass = Amixin(Bmixin(Cmixin(vehical)))
//class SuperClass extends Amixin(Bmixin(Cmixin(vehical))) {}效果一样
let car = new SuperClass()
car.A()
car.B()
car.C()

父类 到子类,反直觉的,方法和行为是越来越多的

在类上定义 迭代器 和生成器

javascript 复制代码
//在类上定义一个生成器和默认迭代器
class GenAndIter {
    
//在原型上定义

    * generator() {
        yield 1;
        yield 2;
        yield 3;
    }
    
//在类上定义(静态方法)

    static * generator1() {
        yield 4
        yield 5
        yield 6
    }
    
//定义一个默认迭代器让这个类称为可迭代对象

    *
Symbol.iterator
 {
        yield 7
        yield 8
        yield 9
    }
}
const myGen = new GenAndIter()
for (let i of myGen.generator()) {
    console.log(i)
}
for (let i of GenAndIter.generator1()) {
    console.log(i)
}
for (let i of myGen) {
    console.log(i)
}

Number.EPSILON

Number.EPSILON

js中的最小精度,一般用于判断浮点数运算的误差

arduino 复制代码
false
true

二进制和八进制

0b二进制(binary) 0o八进制 0x十六进制

ini 复制代码
let b = 0b1010
let o = 0o777
let d = 100
let x = 0x

更多

是Number的方法,不是实例方法

javascript 复制代码
Number.inFinite()
Number.isNaN()
Number.parseInt()
Number.parseFloat()
Number.isInteger()//是否整数
Number.trunc()//抹掉小数
Number.sign()

Bigint 大整型 globalThis

必须是整数,而且Bigint不能和int运算,必须和Bigint才能运算

ini 复制代码
let n = 512n
int m = 123
console.log(Biging(n))

globalThis

  • 始终执行全局对象
  • 在浏览器环境下指向window
  • 在js的node环境下指向global
相关推荐
斯~内克9 分钟前
前端浏览器窗口交互完全指南:从基础操作到高级控制
前端
Mike_jia1 小时前
Memos:知识工作者的理想开源笔记系统
前端
前端大白话1 小时前
前端崩溃瞬间救星!10 个 JavaScript 实战技巧大揭秘
前端·javascript
loveoobaby1 小时前
Shadertoy着色器移植到Three.js经验总结
前端
蓝易云1 小时前
在Linux、CentOS7中设置shell脚本开机自启动服务
前端·后端·centos
浩龙不eMo1 小时前
前端获取环境变量方式区分(Vite)
前端·vite
一千柯橘1 小时前
Nestjs 解决 request entity too large
javascript·后端
土豆骑士1 小时前
monorepo 实战练习
前端
土豆骑士1 小时前
monorepo最佳实践
前端
见青..1 小时前
【学习笔记】文件包含漏洞--本地远程包含、伪协议、加密编码
前端·笔记·学习·web安全·文件包含