JS语言特性(下)

共 11574字,需浏览 24分钟

 ·

2021-09-28 12:22

作者:小坏坏

来源:SegmentFault 思否社区

调包

即引用其他项目或者文件。

之所以需要把这个模块单独拎出来,是因为,一个语言能不能成气候的其中的一个关键点在于能否模块化;一个项目能否形成一个可观的体量也离不开模块化,简单来说就是不同文件或项目间能否互相调用,es5和es6中都有着不同风格的引用方式,开发时要注意自己的开发环境以及语法格式

es5

有着AMD、CMD、CommonJS三种的引用方式,其中AMD(Asynchronous Module Definition),CMD(Common Module Definition),是为了解决前端同步引用的过高延迟会产生的“假死”状态而诞生的异步引用方式。

就本人而言,前端开发已基本上全面使用es6格式语法,AMD、CMD格式的引用几乎不用,此处不做详细讲解,详情查看链接,只讲解后端常用的CommonJS

CommonJS的发展历史在此:https://zhuanlan.zhihu.com/p/113009496

CommonJS,导出有着exportsmodule.exports两种方式,二者是完全等效的,但是绝大部分时候,为和es6的export区分开,只使用module.exports

调用过程

CommonJS的调用为同步调用,在调用时,执行调用文件中的全部可执行的部分;同步引用在后端可以忽略传输的时延,但是前端同步+高延迟意味着浏览器要卡着等待接收到这个文件才能继续进行,这便有了上文提到的异步加载方式AMD和CMD

//fileA.js 导出
console.log("haoye1")
function a(){
    console.log("haoye2")
}
a();
module.exports={a}

//b.js 引入
const fileA=require("fileA")
// 控制台输出:
// haoye1
// haoye2


基本用法

代码如下

//fileA.js 导出
function a(){
    console.log("haoye")
}
const b=()=>{
    console.log("haoyeB")
}
const c=0;
module.exports={//本质上是将需要导出的值包裹成json变量赋予modules.exports
    a:a,
    b,//json格式若变量key和value的名字一样可以直接缩写
    c
}
//b.js 引入
const fileA=require("fileA")
fileA.a();//haoye
fileA.b();//haoyeB
//或者
const {a,b}=require("fileA")
a();//haoye
b();//haoyeB


值调用

引用值时,是以变量的形式引入的(letconstvar),所以引用后的变量是否可修改以变量的定义为准,一般建议以const格式引用;

在调用一般类型的参数(numberboolean等)为深拷贝,模块内的变量与引用后的变量无关联;但是在调用object类型参数时为浅拷贝,模块内变量与引用后的值是同步的;

// b.js
let count = 1//number类
let obj = {//object类
    count: 1
}
let plusCount = () => {
    count++
}
let plusCount2 = () => {
    obj.count++
}
setTimeout(() => {
    console.log(count)//(1s后)2
    console.log(obj.count)//(1s后)2
}, 1000)
module.exports = {
    obj,
    count,
    plusCount
}

// a.js
let mod = require('./b.js')
console.log(mod.count)//1
console.log(mod.obj.count)//1
mod.plusCount()
mod.plusCount2()
console.log(mod.count)//1
console.log(mod.obj.count)//2
setTimeout(() => {
    mod.count = 3
    mod.obj.count = 3
    console.log(mod.count)//(2s后)3
    console.log(mod.obj.count)//(2s后)3
}, 2000)

es6

es6的导出为export

调用过程

同CommonJS,为同步调用,调用时先执行调用文件中的全部可执行的部分

基本用法

有多行导出、统一导出、默认导出三种格式

// fileA.js
// 多行导出
export function a() {
  console.log("haoye")
}
export const b = () => {
  console.log("haoyeB")
}
// fileB.js
// 统一导出
function a() {
  console.log("haoye")
}
const b = () => {
  console.log("haoyeB")
}
export {
    a,
    b
}
// fileC.js
// 默认导出
function a() {
  console.log("haoye")
}
const b = () => {
  console.log("haoyeB")
}
export default {//此种导出方式只能全部调用,不可只调用其中某个函数
    a,
    b
}
// fileD.js 导入演示
import { a, b } from 'fileA'//或fileB
a();//haoye
b();//haoyeB

// fileE.js 导入演示
import D from 'fileD'
import { a, b } from 'fileD'// 报错
D.a();//haoye
D.b();//haoyeB


值调用

默认所有的值以const变量形式引入(即不可修改),但同样可将object类型变量内的数据加以修改,并在原模块内依然产生影响

// fileA.js
export let counter = {
  count: 1
}
// fileB.js
import { counter } from 'fileA'
counter.count++;
console.log(counter.count)//2
counter = {}// 报错: "counter" is read-only

异步

回调

为保证某一函数的正常执行,需要在其执行完毕后对其结果进行判断,而判断这个过程所执行的函数即为回调函数

例如一个简单的a+b的函数,通常情况下,我们可能会这么写:

function onSuccess(result){
    console.log("haoye ", result)
}
function onFailed(error){
    console.log("huaiye ", error)
}
function aPlusB(a,b){
    const c=a+b
    if(c===(Number(a)+Number(b))){//防止a或b为其他类型如字符串,在此验证
        onSuccess(c)
    }else{
        onFailed(c)
    }
}
const a=1,b=2;
aPlusB(a, b);// haoye 3

但是当我想把这个功能独立成模块,随时随地都可以处理其结果时,我们可能会这么写:

const a=1,b=2;
function aPlusB(a,b){
    const c=a+b
    return c
}
const c=aPlusB(a, b);
if(c===(Number(a)+Number(b))){
    onSuccess(c)
}else{
    onFailed(c)
}


可随意处理其结果的代价便是,必须要将结果返回,且判断语句必须写在外面;但也许可以这样写

function aPlusB(a,b){
    const c=a+b
    if(c===(Number(a)+Number(b))){//防止a或b为其他类型如字符串,在此验证
        return {c,succes:true}
    }else{
        return {c,succes:false}
    }
}
const a=1,b=2;
const res=aPlusB(a, b);
if(res.succes){
    onSuccess(res.c)
}else{
    onFailed(res.c)
}


肉眼可见的降低了程序的可读性,并且同样需要等待函数执行完,否则无法执行接下来可能有的b+c,c+d等函数,若接下来要执行的函数根本用不到上一步所计算出的结果,那么等待便是完全无意义的

js中的回调

不过好在js可以以参数的形式声明一个函数,这样就不需要等待回调也执行完,才执行下一步函数,于是有如下写法

function aPlusB(a,b,onSuccess,onFailed){
    const c=a+b
    if(c===(Number(a)+Number(b))){//防止a或b为其他类型如字符串,在此验证
        onSuccess(c)
    }else{
        onFailed(c)
    }
}
function onSuccess(result){
    console.log("haoye ", result)
}
function onFailed(error){
    console.log("huaiye ", error)
}
const a=1,b=2;
aPlusB(a,b,onSuccess,onFailed)


可以简化为es6的箭头函数形式

aPlusB(a,b,(result)=>{
    console.log("haoye ", result)
},(error)=>{
    console.log("huaiye ", error)
})


这样以来,就不影响接下来的b+c,c+d了,并且回调函数的定义也更加的灵活;

但现在又有了另一个问题,如果我接下来的b+c需要用到这一步的结果,比如我需要求a+b+c,那么我可能要这么写

const c=3
aPlusB(a,b,(result)=>{
    aPlusB(result,c,(result2)=>{
        console.log("haoye ", result2)
    },(error)=>{
        console.log("huaiye ", error)
    })
},(error)=>{
    console.log("huaiye ", error)
})


这是只需要+c的情况,但是如果我需要求a+b+c+d+e+f...呢?粗略展示下代码

const c=3,d=4,e=5
aPlusB(a,b,(res)=>{
    aPlusB(res,c,(res2)=>{
       aPlusB(res2,d,(res3)=>{
           aPlusB(res4,e,(res4)=>{
               aPlusB(res4,f,res5=>{
                   console.log("haoye ", res5)
               },err=>{
                   console.log("huaiye ", err)
               })
           },err=>{
               console.log("huaiye ", err)
           })
       },err=>{
           console.log("huaiye ", err)
       })
    },(err)=>{
        console.log("huaiye ", err)
    })
},(error)=>{
    console.log("huaiye ", err)
})


这就陷入了一种”回调地狱“,最直观的来讲,便是降低了代码的可读性和可维护性,使代码变得臃肿、低效,例如err,其实只要其中任何一步丢出了错误,那么接下来全部的错误捕获函数都是无意义的,但是为了程序的稳定性,这些函数都必须要被定义,于是便有了es6中的——

Promise

中文翻译过来便是承诺,意为在未来某一个时间点承诺返回数据给你。

promise的详细介绍在此:https://www.runoob.com/w3cnote/javascript-promise-object.html

此处仅对promise的使用做简单描述

  • Promise有三种状态:pending/reslove/reject 。pending就是未决,resolve可以理解为成功,reject可以理解为拒绝。

  • Promise常用的几种方法 then 表示异步成功执行后的数据状态变为reslove, catch 表示异步失败后执行的数据状态变为reject ,all表示把多个没有关系的Promise封装成一个Promise对象使用then返回一个数组数据,finally表示无论成功还是失败,全部执行完毕。


还是上面的a+b函数,这次使用promise的方式来实现
function aPlusB(a,b){
    return new Promise((onSuccess, onFailed)=>{
        const c=a+b
        if(c===(Number(a)+Number(b))){//防止a或b为其他类型如字符串,在此验证
            onSuccess(c)
        }else{
            onFailed(c)
        }
    })
}
const a=1,b=2;
aPlusB(a,b).then(res=>{//成功后执行的函数
    console.log("haoye ", res)
},err1=>{//失败后执行的函数,可不定义,和catch至少有一个需要定义,否则抛出的错误无法捕获
    console.log("huaiye ", err)
}).catch(err2=>{//等效于上方的err1=>{},但是此处还可以抓取其他为能预测到的问题
    console.log("huaiye ", err2)
})

那么这种形式会如何应对a+b+c+d+e+f...呢?
const a=1,b=2,c=3,d=4,e=5
aPlusB(a,b).then(res=>{//成功后执行的函数
   return aPlusB(res, b)//若return为一般变量,则下一个then的res为改变量;若return为promise变量,则下一个res为该promise最终return的变量
}).then(res=>{
    return aPlusB(res, c)
}).then(res=>{
    return aPlusB(res, d)
}).then(res=>{
    return aPlusB(res, e)
}).then(res=>{
    return aPlusB(res, f)
}).then(res=>{
    console.log("haoye ", res)
}).catch(err=>{
    console.log("huaiye ", err)
})

优势很明显,但问题也存在,当抛出错误时,我们可能不知道错误具体在哪一步,但是可以通过在可能出错的一步上添加onRejected回调
但是 return new Promise((r,e)=>{})这种函数定义方式可能不是那么的优雅,于是在即将推出的es7标准中(此功能目前的es6中已可以使用),新增asyncawait这两个语法糖,可以使返回复杂的promise类以普通函数的形式编写,以上函数可简写为
async function aPlusB(a,b){
    const c=a+b
    if(c===(Number(a)+Number(b))){
        return c// 正确则返回结果
    }else{
        throw new Error(c)// 错误则抛出错误
    }
}

若希望此函数以同步的方式运行则需要await关键词
const c=await aPlusB(a, b)

但是await只能在异步函数中使用,只能对异步函数使用

单线程

js有着高效的运行速度,但其却是一种单线程语言,js的单线程运行机制在此,但这也带来了很多麻烦,因为服务器后台有着多线程处理的刚需
于是,nodejs为解决多线程的需求,在node10之后,有了稳定的worker解决方案,但worker并没有给js产生第二个线程,而是启动了一个新的进程,进程之间使用json格式数据来进行通讯


点击左下角阅读原文,到 SegmentFault 思否社区 和文章作者展开更多互动和交流,扫描下方”二维码“或在“公众号后台回复“ 入群 ”即可加入我们的技术交流群,收获更多的技术文章~

- END -


浏览 15
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报