Javascript 中产生值的几种方式

178 阅读4分钟

介绍

JavaScript 作为前端开发的必备语言,运行在浏览器这样的复杂交互环境下面,由各种事件驱动产生数据变更、UI更新,理解它的一些底层原理,能够让我们在开发的过程中能够快速的找到解决问题的最佳方式。

在产生值的方式上面就有以下的几种:

单值 多值
被动 同步函数调用 生成器函数
主动 Promise Observable

这里的主动/被动的实体是被调用的函数过程

正文

函数调用

函数调用是最为基本的计算值的方式了

function getValue(){
    return 1 + 2 + 3
}

console.log(getValue()) // --> 6

函数本身知道自己如何计算出一个值,但是决定取这个值的时机掌握在调用者这里,并且是一次调用只能返回一次结果,所以是一种被动的单值产生过程。

生成器函数

生成器是一种带有调用状态的函数调用过程

function *genFn(){
    yield 1;
    yield 2;
    yield 3;
    return 4;
}

const it = genFn();
console.log(it.next()) // {value: 1, done: false}
console.log(it.next()) // {value: 2, done: false}
console.log(it.next()) // {value: 3, done: false}
console.log(it.next()) // {value: 4, done: true}

for(let i of genFn()){
    console.log(i)
}
// 1
// 2
// 3 
// 只有 yield 出来的,done 为 false 才会出现在这里

调用方通过调用这个带有状态的函数对象就可以将其内部的代码执行到下一个yield的地方,暂停执行并获得yield返回的一个值,生成器函数里可以多次 yield 值出来。所以是一种被动的多值产生过程。

其实只要满足迭代协议,任何对象都可以是一个生成器,并使用for ... of来取值,也可以自己实现这样的功能:

const obj = {
  i: 0,
  over() {
    return this.i > 10;
  },
  [Symbol.iterator]() {
    return {
      next: () => {
        if (this.over()) return { done: true, value: this.i };
        return { done: false, value: this.i++ };
      }
    };
  }
};

for (let i of obj) {
  console.log(i);
}
// 输出 0 — 10 

Promise

在将Promise之前,还是先说说回调。

在网络应用中,异步的过程是非常常见的。早期的JS就是使用了回调函数的这种方法,在回调中进行下一步的操作。 通常是这种形式。

setTimeout(()=>{
    dosomething();
    setTimeout(()=>{
        dosomething2();
        setTimeout(()=>{
            dosomething3();
        },1000)
    },1000);
},1000)

ajax({
    method:'get',
    url: 'xxxx',
    params: {...},
    header: {...},
    onSuccess(){
        // 进一步操作
        ajax({
            ...
        })
    },
    onFail(){},
    onFinally(){},
})

可以看到这种方式的局限,本来应该是顺序的逻辑,却必须要嵌套起来写,代码很容易写出来回调地狱,横向形成一个小山峰,有时候连自己光标所处的那一行的上下文都不知道。

这时候就由 Promise 来拯救了。

const wait = (time) => {
  return new Promise(res => setTimeout(res, time));
};

// 使用#Promise.then 来产生一个异步的程序流,代码更具语义,流畅
wait(3000)
  .then(() => {
    console.log(1);
    return wait(3000);
  })
  .then(() => {
    console.log(2);
    return wait(3000);
  })
  .then(() => {
    console.log(3);
  });
  
// 使用 async/await 的语法糖,等价于上面的代码,更简洁
// 将异步的代码逻辑以同步的书写方式展现出来。
(async () => {
  await wait(3000);
  console.log(1);
  await wait(3000);
  console.log(2);
  await wait(3000);
  console.log(3);
})();

可以看到 Promise 的这种同/异步产生值的方式,可以轻易的将同步异步的代码柔和在一起使用,产生值的时机有Promise决定,每个Promise只能产生一个值,所以这是一种主动的单值产生过程。

Observable

在浏览器中产生信息的可能是计数器、用户交互、网络请求,这些都是可观察的,能被进一步处理,并且通常是可以被触发多次,被多个观察者监听,并多次处理。

在复杂的应用场景下面,程序之中状态受用户交互事件的影响,而状态的这种依赖关系也会影响下游的状态,这种场景下具有很强的灵活性,状态已经不仅仅只是一个变量了,而是一个带有上下文的管道中间件。

整个程序有这些状态流、管道中间件组成,就像是一个电路,状态是个电路上面的元器件,事件就是电源,带动了整个状态树的变化,最后输出到视图或者IO。

著名的实现就有 rxjs ,并且已经进入到了TC39 JavaScript的新特性提案中。

参考

Rxjs 中关于 Observable 的介绍