JavaScript高级程序设计第4版-第 10~11 章

65 阅读4分钟

第十章,函数

散碎

  1. caller 可以找到哪个函数环境中调用了此函数,(不是 this(严格模式不可用
function func(){
    foo()
}
function foo(){
    console.log(foo.caller === func, this === globalThis) // true true
}
func()
  1. function 声明提升并赋值
foo()
fun() // TypeError: fun is not a function
function foo(){ }
var fun = function(){ }

定义函数

new Function('x', 'y', 'return x + y') //(不推荐)

箭头函数

不能使用 arguments、super、new.target、prototype、new

函数名

  1. 一般情况
函数引用.name // 函数名标识符
foo.name // 'foo'
  1. 三种特殊情况
new Function().name // 'anonymous'
func.bind().name // bound func

function foo() {} 
let obj = { 
 get age() { }, 
 set age(newAge) { } 
} 
let propertyDescriptor = Object.getOwnPropertyDescriptor(obj, 'age'); 
console.log(propertyDescriptor.get.name); // get age
console.log(propertyDescriptor.set.name); // set age

函数参数

  1. arguments[0] 就是第一个参数,arguments[1] 就是第二个参数...
  2. 在非严格模式下,改变 arguements[0] 的值, 会影响第一个具名参数的值,反之亦然。(但并不意味着他们访问同一地址,只是他们会同步;且传入几个参数,那么只有这几个参数会保持同步,后面声明但未传的不会同步)
  3. 严格模式下他们值初始相同,但后续并不同步
  4. 函数参数有自己的作用域,他不能使用函数体的内容,但是以下代码没有问题
const n = 3
function foo(x = 1, y = x * 2, z = n) {}
  1. arguments.callee === 函数本身 (严格模式不可用 callee)

arguments 长度严格取决于 传入的参数,与定义了多少参数无关

尾调用

目前大部分环境都还未实现尾调用的优化;尾调用指的是最后直接返回函数的调用,且外部栈帧真的没必要存在了。

// 是
function dfs(x){
  return dfs(x + 1)
}
dfs(1)

// 不是
function dfs(x){
  return dfs(x) + 1
}
dfs(1)

闭包

闭包的产生,即内部作用域种用到了外部作用域变量 请仔细阅读下方代码

  1. 不用外部函数作用域变量
const overall = 'overall'
function container(){
  const x = 'x'
  function inner(){
    const y = 'y'
    return function content(){
      const z = 'z'
    }
  }
  return inner()
}
console.dir(container())

image.png

  1. 用外部函数作用域变量
const overall = 'overall'
function container(){
  const x = 'x'
  function inner(){
    const y = 'y'
    return function content(){
      const z = 'z'
      const closure = x + y
    }
  }
  return inner()
}
console.dir(container())

image.png

[[Scopes]] 列表就是作用域链的体现,具体原理就是,词法环境种引用了外部作用域的词法环境,由此链条依次向上查找,构成作用域链。

第十一章,期约与异步函数

散碎

.resolve() 是幂等的,.reject() 不是
const origin = Promise.resolve('inner')

const p = Promise.resolve(origin)
console.log(p === origin, p) // true Promise<fulfilled>: 'inner'

const p2 = Promise.reject(origin)
console.log(p2 === origin, p2) // false Promise<rejected>: Promise
外部 try catch 无法捕捉错误
try {
    Promise.reject(1)
} catch (error) {
    console.log(error) // 捕获不到,会报错
}
多个串联的期约处理,可以理解为树接口,执行顺序可以理解为层序遍历
//      A 
//    /   \ 
//   B     C 
//  / \   / \ 
// D   E F   G 
let A = new Promise((resolve, reject) => { 
 console.log('A'); 
 resolve(); 
}); 
let B = A.then(() => console.log('B')); 
B.then(() => console.log('D')); 
let C = A.then(() => console.log('C')); 
C.then(() => console.log('F')); 
B.then(() => console.log('E')); 
C.then(() => console.log('G')); 
// A B C D E F G
有方法会静默处理错误(.all、.race

后续解决或拒绝的期约将不再影响返回的期约的状态。这种行为称为“静默处理”,意味着后续的拒绝不会触发任何错误或被捕获的拒绝处理程序。(catch会触发的话,只能被触发一次

let p = Promise.race([Promise.reject(1), new Promise((_, reject) => reject(2))])
p.catch(reason => setTimeout(console.log, 0, reason)) // 1 (之后不再处理2
串行期约组合合并处理
const add_2 = x => x + 2
const add_4 = x => x + 4
const add_8 = x => x + 8
const fns = [add_2, add_4, add_8]
const compose = fns => {
  return x => fns.reduce((p, fn) => p.then(fn), Promise.resolve(x))
}
const flow = compose(fns)
flow(1).then(result => {
  console.log(result)
})

期约取消与进度

es6+ 并没有实现取消与进度,委员会认为取消与进度是激进的;如果实现取消与进度,会让期约连锁和合成过度复杂

期约取消

以下借鉴进度的写法实现取消

<button id="start">Start</button>
<button id="cancel">Cancel</button>
<script>
  const startBtn = document.querySelector('#start')
  const cancelBtn = document.querySelector('#cancel')
  class CouldCancelPromise {
    constructor(executor) {
      // 保留原来的 resolve,reject 并添加 cancel 函数
      return new Promise((resolve, reject) => {
        executor(resolve, reject, resolve)
      })
    }
  }
  startBtn.addEventListener('click', () => {
    console.log('wait...')
    const cancelPromise = new CouldCancelPromise((resolve, reject, cancel) => {
      const id = setTimeout(resolve, 3000, 'done')
      cancelBtn.addEventListener('click', () => {
        cancel('cancel')
        clearInterval(id)
      })
    })
    cancelPromise.then(result => {
      console.log(result)
    })
  })
</script>

也可以采用继承的形式(不推荐继承内置对象);推荐采用Promise.race;AbortController;第三方promise库这三种方式来实现

class CouldCancelPromise extends Promise {
  constructor(executor) {
    super((resolve, reject) => {
      executor(resolve, reject, resolve)
    })
  }
}
期约进度,就如 fetch 的下载进度一样,大致代码如下
class ProgressPromise extends Promise{
  constructor(executor) {
    const notifyHandlers = []
    super((resolve, reject) => {
       return executor(resolve, reject, progress => {
        notifyHandlers.forEach(notify => {
          notify(progress)
        })
      })
    })
    this.notifyHandlers = notifyHandlers
  }
  notify = function (fun) {
    this.notifyHandlers.push(fun)
    return this
  }
}
const p = new ProgressPromise((resolve, reject, notify) => {
  function getData(step) {
    if (step < 5) {
      notify(`${step * 20} %.........`)
      setTimeout(getData, 1000, step + 1)
    } else {
      resolve('done')
    }
  }
  getData(0)
})
p.notify(progress => {
  console.log(progress)
})
p.then(res => {
  console.log(res)
})
/*
20 %.........
40 %.........
60 %.........
80 %.........
done
*/

异步函数

async

用于定义异步函数,只在异步函数内才能使用 await

await

await 可以展平 thenable 对象

async function foo() {
  const origin = 'xz'
  const obj = { }
  const thenable = {
    then(callback) {
      callback('thenable')
    }
  }
  const p = Promise.resolve('resolve')
  console.log(await origin, await obj, await thenable, await p)
  await Promise.reject('reject')
  console.log('end') // 不执行
}
foo().catch(console.log)
// xz {} thenable resolve reject

实现 sleep

function sleep(delay){
  return new Promise(resolve => setTimeout(resolve, delay))
}
async function main(){
  console.log(1)
  await sleep(1000)
  console.log(2)
}
main()