JS語言特性(下)
作者:小壞壞
來源:SegmentFault 思否社區(qū)
調(diào)包
即引用其他項目或者文件。
之所以需要把這個模塊單獨拎出來,是因為,一個語言能不能成氣候的其中的一個關(guān)鍵點在于能否模塊化;一個項目能否形成一個可觀的體量也離不開模塊化,簡單來說就是不同文件或項目間能否互相調(diào)用,es5和es6中都有著不同風(fēng)格的引用方式,開發(fā)時要注意自己的開發(fā)環(huán)境以及語法格式
es5
有著AMD、CMD、CommonJS三種的引用方式,其中AMD(Asynchronous Module Definition),CMD(Common Module Definition),是為了解決前端同步引用的過高延遲會產(chǎn)生的“假死”狀態(tài)而誕生的異步引用方式。
就本人而言,前端開發(fā)已基本上全面使用es6格式語法,AMD、CMD格式的引用幾乎不用,此處不做詳細(xì)講解,詳情查看鏈接,只講解后端常用的CommonJS,
CommonJS的發(fā)展歷史在此:https://zhuanlan.zhihu.com/p/113009496
CommonJS,導(dǎo)出有著exports和module.exports兩種方式,二者是完全等效的,但是絕大部分時候,為和es6的export區(qū)分開,只使用module.exports
調(diào)用過程
CommonJS的調(diào)用為同步調(diào)用,在調(diào)用時,執(zhí)行調(diào)用文件中的全部可執(zhí)行的部分;同步引用在后端可以忽略傳輸?shù)臅r延,但是前端同步+高延遲意味著瀏覽器要卡著等待接收到這個文件才能繼續(xù)進(jìn)行,這便有了上文提到的異步加載方式AMD和CMD
//fileA.js 導(dǎo)出
console.log("haoye1")
function a(){
console.log("haoye2")
}
a();
module.exports={a}
//b.js 引入
const fileA=require("fileA")
// 控制臺輸出:
// haoye1
// haoye2
基本用法
代碼如下
//fileA.js 導(dǎo)出
function a(){
console.log("haoye")
}
const b=()=>{
console.log("haoyeB")
}
const c=0;
module.exports={//本質(zhì)上是將需要導(dǎo)出的值包裹成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
值調(diào)用
引用值時,是以變量的形式引入的(let,const,var),所以引用后的變量是否可修改以變量的定義為準(zhǔn),一般建議以const格式引用;
在調(diào)用一般類型的參數(shù)(number,boolean等)為深拷貝,模塊內(nèi)的變量與引用后的變量無關(guān)聯(lián);但是在調(diào)用object類型參數(shù)時為淺拷貝,模塊內(nèi)變量與引用后的值是同步的;
// 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的導(dǎo)出為export
調(diào)用過程
同CommonJS,為同步調(diào)用,調(diào)用時先執(zhí)行調(diào)用文件中的全部可執(zhí)行的部分
基本用法
有多行導(dǎo)出、統(tǒng)一導(dǎo)出、默認(rèn)導(dǎo)出三種格式
// fileA.js
// 多行導(dǎo)出
export function a() {
console.log("haoye")
}
export const b = () => {
console.log("haoyeB")
}
// fileB.js
// 統(tǒng)一導(dǎo)出
function a() {
console.log("haoye")
}
const b = () => {
console.log("haoyeB")
}
export {
a,
b
}
// fileC.js
// 默認(rèn)導(dǎo)出
function a() {
console.log("haoye")
}
const b = () => {
console.log("haoyeB")
}
export default {//此種導(dǎo)出方式只能全部調(diào)用,不可只調(diào)用其中某個函數(shù)
a,
b
}
// fileD.js 導(dǎo)入演示
import { a, b } from 'fileA'//或fileB
a();//haoye
b();//haoyeB
// fileE.js 導(dǎo)入演示
import D from 'fileD'
import { a, b } from 'fileD'// 報錯
D.a();//haoye
D.b();//haoyeB
值調(diào)用
默認(rèn)所有的值以const變量形式引入(即不可修改),但同樣可將object類型變量內(nèi)的數(shù)據(jù)加以修改,并在原模塊內(nèi)依然產(chǎn)生影響
// 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異步
回調(diào)
為保證某一函數(shù)的正常執(zhí)行,需要在其執(zhí)行完畢后對其結(jié)果進(jìn)行判斷,而判斷這個過程所執(zhí)行的函數(shù)即為回調(diào)函數(shù)
例如一個簡單的a+b的函數(shù),通常情況下,我們可能會這么寫:
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
但是當(dāng)我想把這個功能獨立成模塊,隨時隨地都可以處理其結(jié)果時,我們可能會這么寫:
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)
}
可隨意處理其結(jié)果的代價便是,必須要將結(jié)果返回,且判斷語句必須寫在外面;但也許可以這樣寫
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)
}
肉眼可見的降低了程序的可讀性,并且同樣需要等待函數(shù)執(zhí)行完,否則無法執(zhí)行接下來可能有的b+c,c+d等函數(shù),若接下來要執(zhí)行的函數(shù)根本用不到上一步所計算出的結(jié)果,那么等待便是完全無意義的
js中的回調(diào)
不過好在js可以以參數(shù)的形式聲明一個函數(shù),這樣就不需要等待回調(diào)也執(zhí)行完,才執(zhí)行下一步函數(shù),于是有如下寫法
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的箭頭函數(shù)形式
aPlusB(a,b,(result)=>{
console.log("haoye ", result)
},(error)=>{
console.log("huaiye ", error)
})
這樣以來,就不影響接下來的b+c,c+d了,并且回調(diào)函數(shù)的定義也更加的靈活;
但現(xiàn)在又有了另一個問題,如果我接下來的b+c需要用到這一步的結(jié)果,比如我需要求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)
})
這就陷入了一種”回調(diào)地獄“,最直觀的來講,便是降低了代碼的可讀性和可維護(hù)性,使代碼變得臃腫、低效,例如err,其實只要其中任何一步丟出了錯誤,那么接下來全部的錯誤捕獲函數(shù)都是無意義的,但是為了程序的穩(wěn)定性,這些函數(shù)都必須要被定義,于是便有了es6中的——
Promise
中文翻譯過來便是承諾,意為在未來某一個時間點承諾返回數(shù)據(jù)給你。
promise的詳細(xì)介紹在此:https://www.runoob.com/w3cnote/javascript-promise-object.html
此處僅對promise的使用做簡單描述
Promise有三種狀態(tài):pending/reslove/reject 。pending就是未決,resolve可以理解為成功,reject可以理解為拒絕。Promise常用的幾種方法then表示異步成功執(zhí)行后的數(shù)據(jù)狀態(tài)變?yōu)閞eslove,catch表示異步失敗后執(zhí)行的數(shù)據(jù)狀態(tài)變?yōu)閞eject ,all表示把多個沒有關(guān)系的Promise封裝成一個Promise對象使用then返回一個數(shù)組數(shù)據(jù),finally表示無論成功還是失敗,全部執(zhí)行完畢。
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=>{//成功后執(zhí)行的函數(shù)
console.log("haoye ", res)
},err1=>{//失敗后執(zhí)行的函數(shù),可不定義,和catch至少有一個需要定義,否則拋出的錯誤無法捕獲
console.log("huaiye ", err)
}).catch(err2=>{//等效于上方的err1=>{},但是此處還可以抓取其他為能預(yù)測到的問題
console.log("huaiye ", err2)
})
const a=1,b=2,c=3,d=4,e=5
aPlusB(a,b).then(res=>{//成功后執(zhí)行的函數(shù)
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)
})
return new Promise((r,e)=>{})這種函數(shù)定義方式可能不是那么的優(yōu)雅,于是在即將推出的es7標(biāo)準(zhǔn)中(此功能目前的es6中已可以使用),新增async,await這兩個語法糖,可以使返回復(fù)雜的promise類以普通函數(shù)的形式編寫,以上函數(shù)可簡寫為async function aPlusB(a,b){
const c=a+b
if(c===(Number(a)+Number(b))){
return c// 正確則返回結(jié)果
}else{
throw new Error(c)// 錯誤則拋出錯誤
}
}
await關(guān)鍵詞const c=await aPlusB(a, b)
await只能在異步函數(shù)中使用,只能對異步函數(shù)使用單線程

