整合營銷服務(wù)商

          電腦端+手機(jī)端+微信端=數(shù)據(jù)同步管理

          免費(fèi)咨詢熱線:

          前端基礎(chǔ):培訓(xùn)機(jī)構(gòu)關(guān)于JavaScript面試題匯總

          前端基礎(chǔ):培訓(xùn)機(jī)構(gòu)關(guān)于JavaScript面試題匯總,面試必備(背)!

          接:https://juejin.im/post/5c6ad9fde51d453c356e37d1

          一、變量類型

          1.JS 的數(shù)據(jù)類型分類

          根據(jù) JavaScript 中的變量類型傳遞方式,分為基本數(shù)據(jù)類型和引用數(shù)據(jù)類型。其中基本數(shù)據(jù)類型包括Undefined、Null、Boolean、Number、String、Symbol (ES6新增,表示獨(dú)一無二的值),而引用數(shù)據(jù)類型統(tǒng)稱為Object對象,主要包括對象、數(shù)組和函數(shù)。在參數(shù)傳遞方式上,基本類型是按值傳遞,引用類型是按共享傳遞

          題目:基本類型和引用類型的區(qū)別

          基本類型和引用類型存儲于內(nèi)存的位置不同,基本類型直接存儲在棧中,而引用類型的對象存儲在堆中,與此同時(shí),在棧中存儲了指針,而這個(gè)指針指向正是堆中實(shí)體的起始位置。下面通過一個(gè)小題目,來看下兩者的主要區(qū)別:

          // 基本類型
          var a=10
          var b=a
          b=20
          console.log(a) // 10
          console.log(b) // 20
          

          上述代碼中,a b都是值類型,兩者分別修改賦值,相互之間沒有任何影響。再看引用類型的例子:

          // 引用類型
          var a={x: 10, y: 20}
          var b=a
          b.x=100
          b.y=200
          console.log(a) // {x: 100, y: 200}
          console.log(b) // {x: 100, y: 200}
          

          上述代碼中,a b都是引用類型。在執(zhí)行了b=a之后,修改b的屬性值,a的也跟著變化。因?yàn)閍和b都是引用類型,指向了同一個(gè)內(nèi)存地址,即兩者引用的是同一個(gè)值,因此b修改屬性時(shí),a的值隨之改動(dòng)

          2.數(shù)據(jù)類型的判斷

          1)typeof

          typeof返回一個(gè)表示數(shù)據(jù)類型的字符串,返回結(jié)果包括:number、boolean、string、symbol、object、undefined、function等7種數(shù)據(jù)類型,但不能判斷null、array等

          2)instanceof

          instanceof 是用來判斷A是否為B的實(shí)例,表達(dá)式為:A instanceof B,如果A是B的實(shí)例,則返回true,否則返回false。instanceof 運(yùn)算符用來測試一個(gè)對象在其原型鏈中是否存在一個(gè)構(gòu)造函數(shù)的 prototype 屬性,但它不能檢測null 和 undefined

          3)嚴(yán)格運(yùn)算符===

          只能用于判斷null和undefined,因?yàn)檫@兩種類型的值都是唯一的

          4)constructor

          constructor作用和instanceof非常相似。但constructor檢測 Object與instanceof不一樣,還可以處理基本數(shù)據(jù)類型的檢測。 不過函數(shù)的 constructor 是不穩(wěn)定的,這個(gè)主要體現(xiàn)在把類的原型進(jìn)行重寫,在重寫的過程中很有可能出現(xiàn)把之前的constructor給覆蓋了,這樣檢測出來的結(jié)果就是不準(zhǔn)確的。

          5)Object.prototype.toString.call()

          Object.prototype.toString.call() 是最準(zhǔn)確最常用的方式

          3.淺拷貝與深拷貝

          淺拷貝只復(fù)制指向某個(gè)對象的指針,而不復(fù)制對象本身,新舊對象還是共享同一塊內(nèi)存

          淺拷貝的實(shí)現(xiàn)方式(詳見淺拷貝與深拷貝):

          • Object.assign():需注意的是目標(biāo)對象只有一層的時(shí)候,是深拷貝
          • Array.prototype.concat()
          • Array.prototype.slice()

          深拷貝就是在拷貝數(shù)據(jù)的時(shí)候,將數(shù)據(jù)的所有引用結(jié)構(gòu)都拷貝一份。簡單的說就是,在內(nèi)存中存在兩個(gè)數(shù)據(jù)結(jié)構(gòu)完全相同又相互獨(dú)立的數(shù)據(jù),將引用型類型進(jìn)行復(fù)制,而不是只復(fù)制其引用關(guān)系。

          深拷貝的實(shí)現(xiàn)方式:

          • 熱門的函數(shù)庫lodash,也有提供_.cloneDeep用來做深拷貝
          • jquery 提供一個(gè)$.extend可以用來做深拷貝
          • JSON.parse(JSON.stringify())
          • 手寫遞歸方法

          遞歸實(shí)現(xiàn)深拷貝的原理:要拷貝一個(gè)數(shù)據(jù),我們肯定要去遍歷它的屬性,如果這個(gè)對象的屬性仍是對象,繼續(xù)使用這個(gè)方法,如此往復(fù)。

          二、作用域和閉包

          1.執(zhí)行上下文和執(zhí)行棧

          執(zhí)行上下文就是當(dāng)前 JavaScript 代碼被解析和執(zhí)行時(shí)所在環(huán)境的抽象概念, JavaScript 中運(yùn)行任何的代碼都是在執(zhí)行上下文中運(yùn)行。 執(zhí)行上下文的生命周期包括三個(gè)階段:創(chuàng)建階段→執(zhí)行階段→回收階段,我們重點(diǎn)介紹創(chuàng)建階段。

          創(chuàng)建階段(當(dāng)函數(shù)被調(diào)用,但未執(zhí)行任何其內(nèi)部代碼之前)會做以下三件事:

          • 創(chuàng)建變量對象:首先初始化函數(shù)的參數(shù)arguments,提升函數(shù)聲明和變量聲明。
          • 創(chuàng)建作用域鏈:下文會介紹
          • 確定this指向:下文會介紹

          這是因?yàn)楫?dāng)函數(shù)執(zhí)行的時(shí)候,首先會形成一個(gè)新的私有的作用域,然后依次按照如下的步驟執(zhí)行:

          • 如果有形參,先給形參賦值
          • 進(jìn)行私有作用域中的預(yù)解釋,函數(shù)聲明優(yōu)先級比變量聲明高,最后后者會被前者所覆蓋,但是可以重新賦值
          • 私有作用域中的代碼從上到下執(zhí)行

          函數(shù)多了,就有多個(gè)函數(shù)執(zhí)行上下文,每次調(diào)用函數(shù)創(chuàng)建一個(gè)新的執(zhí)行上下文,那如何管理創(chuàng)建的那么多執(zhí)行上下文呢?

          JavaScript 引擎創(chuàng)建了執(zhí)行棧來管理執(zhí)行上下文。可以把執(zhí)行棧認(rèn)為是一個(gè)存儲函數(shù)調(diào)用的棧結(jié)構(gòu),遵循先進(jìn)后出的原則

          • 從上面的流程圖,我們需要記住幾個(gè)關(guān)鍵點(diǎn):JavaScript執(zhí)行在單線程上,所有的代碼都是排隊(duì)執(zhí)行。
          • 一開始瀏覽器執(zhí)行全局的代碼時(shí),首先創(chuàng)建全局的執(zhí)行上下文,壓入執(zhí)行棧的頂部。
          • 每當(dāng)進(jìn)入一個(gè)函數(shù)的執(zhí)行就會創(chuàng)建函數(shù)的執(zhí)行上下文,并且把它壓入執(zhí)行棧的頂部。當(dāng)前函數(shù)執(zhí)行完成后,當(dāng)前函數(shù)的執(zhí)行上下文出棧,并等待垃圾回收。
          • 瀏覽器的JS執(zhí)行引擎總是訪問棧頂?shù)膱?zhí)行上下文。
          • 全局上下文只有唯一的一個(gè),它在瀏覽器關(guān)閉時(shí)出棧。

          2.作用域與作用域鏈

          ES6 到來JavaScript 有全局作用域、函數(shù)作用域和塊級作用域(ES6新增)。我們可以這樣理解:作用域就是一個(gè)獨(dú)立的地盤,讓變量不會外泄、暴露出去。也就是說作用域最大的用處就是隔離變量,不同作用域下同名變量不會有沖突。 在介紹作用域鏈之前,先要了解下自由變量,如下代碼中,console.log(a)要得到a變量,但是在當(dāng)前的作用域中沒有定義a(可對比一下b)。當(dāng)前作用域沒有定義的變量,這成為 自由變量。

          var a=100
          function fn() {
           var b=200
           console.log(a) // 這里的a在這里就是一個(gè)自由變量
           console.log(b)
          }
          fn()
          

          自由變量的值如何得到 —— 向父級作用域(創(chuàng)建該函數(shù)的那個(gè)父級作用域)尋找。如果父級也沒呢?再一層一層向上尋找,直到找到全局作用域還是沒找到,就宣布放棄。這種一層一層的關(guān)系,就是作用域鏈 。

          function F1() {
           var a=100
           return function () {
           console.log(a)
           }
          }
          function F2(f1) {
           var a=200
           console.log(f1())
          }
          var f1=F1()
          F2(f1) // 100 
          

          上述代碼中,自由變量a的值,從函數(shù)F1中查找而不是F2,這是因?yàn)?strong>當(dāng)自由變量從作用域鏈中去尋找,依據(jù)的是函數(shù)定義時(shí)的作用域鏈,而不是函數(shù)執(zhí)行時(shí)。

          3.閉包是什么

          閉包這個(gè)概念也是JavaScript中比較抽象的概念,我個(gè)人理解,閉包是就是函數(shù)中的函數(shù)(其他語言不能這樣),里面的函數(shù)可以訪問外面函數(shù)的變量,外面的變量的是這個(gè)內(nèi)部函數(shù)的一部分。

          閉包的作用:

          • 使用閉包可以訪問函數(shù)中的變量。
          • 可以使變量長期保存在內(nèi)存中,生命周期比較長

          閉包不能濫用,否則會導(dǎo)致內(nèi)存泄露,影響網(wǎng)頁的性能。閉包使用完了后,要立即釋放資源,將引用變量指向null。

          閉包主要有兩個(gè)應(yīng)用場景:

          • 函數(shù)作為參數(shù)傳遞(見作用域部分例子)
          • 函數(shù)作為返回值(如下例)
          function outer() {
           var num=0 //內(nèi)部變量
           return function add() {
           //通過return返回add函數(shù),就可以在outer函數(shù)外訪問了。
           num++ //內(nèi)部函數(shù)有引用,作為add函數(shù)的一部分了
           console.log(num)
           }
          }
          var func1=outer() //
          func1() //實(shí)際上是調(diào)用add函數(shù), 輸出1
          func1() //輸出2
          var func2=outer()
          func2() // 輸出1
          func2() // 輸出2
          

          4.this全面解析

          先搞明白一個(gè)很重要的概念 —— this的值是在執(zhí)行的時(shí)候才能確認(rèn),定義的時(shí)候不能確認(rèn)! 為什么呢 —— 因?yàn)閠his是執(zhí)行上下文環(huán)境的一部分,而執(zhí)行上下文需要在代碼執(zhí)行之前確定,而不是定義的時(shí)候。看如下例子:

          // 情況1
          function foo() {
           console.log(this.a) //1
          }
          var a=1
          foo()
          // 情況2
          function fn(){
           console.log(this);
          }
          var obj={fn:fn};
          obj.fn(); //this->obj
          // 情況3
          function CreateJsPerson(name,age){
          //this是當(dāng)前類的一個(gè)實(shí)例p1
          this.name=name; //=>p1.name=name
          this.age=age; //=>p1.age=age
          }
          var p1=new CreateJsPerson("尹華芝",48);
          // 情況4
          function add(c, d){
           return this.a + this.b + c + d;
          }
          var o={a:1, b:3};
          add.call(o, 5, 7); // 1 + 3 + 5 + 7=16
          add.apply(o, [10, 20]); // 1 + 3 + 10 + 20=34
          // 情況5
          <button id="btn1">箭頭函數(shù)this</button>
          <script type="text/javascript"> 
           let btn1=document.getElementById('btn1');
           let obj={
           name: 'kobe',
           age: 39,
           getName: function () {
           btn1.onclick=()=> {
           console.log(this);//obj
           };
           }
           };
           obj.getName();
          </script>
          

          接下來我們逐一解釋上面幾種情況

          • 對于直接調(diào)用 foo 來說,不管 foo 函數(shù)被放在了什么地方,this 一定是 window
          • 對于 obj.foo() 來說,我們只需要記住,誰調(diào)用了函數(shù),誰就是 this,所以在這個(gè)場景下 foo 函數(shù)中的 this 就是 obj 對象
          • 在構(gòu)造函數(shù)模式中,類中(函數(shù)體中)出現(xiàn)的this.xxx=xxx中的this是當(dāng)前類的一個(gè)實(shí)例
          • call、apply和bind:this 是第一個(gè)參數(shù)
          • 箭頭函數(shù)this指向:箭頭函數(shù)沒有自己的this,看其外層的是否有函數(shù),如果有,外層函數(shù)的this就是內(nèi)部箭頭函數(shù)的this,如果沒有,則this是window。

          三、異步

          1.同步 vs 異步

          同步,我的理解是一種線性執(zhí)行的方式,執(zhí)行的流程不能跨越。比如說話后在吃飯,吃完飯后在看手機(jī),必須等待上一件事完了,才執(zhí)行后面的事情。

          異步,是一種并行處理的方式,不必等待一個(gè)程序執(zhí)行完,可以執(zhí)行其它的任務(wù)。比方說一個(gè)人邊吃飯,邊看手機(jī),邊說話,就是異步處理的方式。在程序中異步處理的結(jié)果通常使用回調(diào)函數(shù)來處理結(jié)果。

          // 同步
          console.log(100)
          alert(200);
          console.log(300) //100 200 300
          
          // 異步
          console.log(100) 
          setTimeout(function(){ 
           console.log(200) 
          }) 
          console.log(300) //100 300 200 
          
          

          2.異步和單線程

          JS 需要異步的根本原因是 JS 是單線程運(yùn)行的,即在同一時(shí)間只能做一件事,不能“一心二用”。為了利用多核CPU的計(jì)算能力,HTML5提出Web Worker標(biāo)準(zhǔn),允許JavaScript腳本創(chuàng)建多個(gè)線程,但是子線程完全受主線程控制,且不得操作DOM。所以,這個(gè)新標(biāo)準(zhǔn)并沒有改變JavaScript單線程的本質(zhì)。

          一個(gè) Ajax 請求由于網(wǎng)絡(luò)比較慢,請求需要 5 秒鐘。如果是同步,這 5 秒鐘頁面就卡死在這里啥也干不了了。異步的話,就好很多了,5 秒等待就等待了,其他事情不耽誤做,至于那 5 秒鐘等待是網(wǎng)速太慢,不是因?yàn)?JS 的原因。

          3.前端異步的場景

          前端使用異步的場景

          • 定時(shí)任務(wù):setTimeout,setInterval
          • 網(wǎng)絡(luò)請求:ajax請求,動(dòng)態(tài)加載
          • 事件綁定

          4.Event Loop

          一個(gè)完整的 Event Loop 過程,可以概括為以下階段:

          • 一開始執(zhí)行棧空,我們可以把執(zhí)行棧認(rèn)為是一個(gè)存儲函數(shù)調(diào)用的棧結(jié)構(gòu),遵循先進(jìn)后出的原則。micro 隊(duì)列空,macro 隊(duì)列里有且只有一個(gè) script 腳本(整體代碼)。
          • 全局上下文(script 標(biāo)簽)被推入執(zhí)行棧,同步代碼執(zhí)行。在執(zhí)行的過程中,會判斷是同步任務(wù)還是異步任務(wù),通過對一些接口的調(diào)用,可以產(chǎn)生新的 macro-task 與 micro-task,它們會分別被推入各自的任務(wù)隊(duì)列里。同步代碼執(zhí)行完了,script 腳本會被移出 macro 隊(duì)列,這個(gè)過程本質(zhì)上是隊(duì)列的 macro-task 的執(zhí)行和出隊(duì)的過程。
          • 上一步我們出隊(duì)的是一個(gè) macro-task,這一步我們處理的是 micro-task。但需要注意的是:當(dāng) macro-task 出隊(duì)時(shí),任務(wù)是一個(gè)一個(gè)執(zhí)行的;而 micro-task 出隊(duì)時(shí),任務(wù)是一隊(duì)一隊(duì)執(zhí)行的。因此,我們處理 micro 隊(duì)列這一步,會逐個(gè)執(zhí)行隊(duì)列中的任務(wù)并把它出隊(duì),直到隊(duì)列被清空。
          • 執(zhí)行渲染操作,更新界面
          • 檢查是否存在 Web worker 任務(wù),如果有,則對其進(jìn)行處理
          • 上述過程循環(huán)往復(fù),直到兩個(gè)隊(duì)列都清空

          接下來我們看道例子來介紹上面流程:

          Promise.resolve().then(()=>{
           console.log('Promise1') 
           setTimeout(()=>{
           console.log('setTimeout2')
           },0)
          })
          setTimeout(()=>{
           console.log('setTimeout1')
           Promise.resolve().then(()=>{
           console.log('Promise2') 
           })
          },0)
          

          最后輸出結(jié)果是Promise1,setTimeout1,Promise2,setTimeout2

          • 一開始執(zhí)行棧的同步任務(wù)(這屬于宏任務(wù))執(zhí)行完畢,會去查看是否有微任務(wù)隊(duì)列,上題中存在(有且只有一個(gè)),然后執(zhí)行微任務(wù)隊(duì)列中的所有任務(wù)輸出Promise1,同時(shí)會生成一個(gè)宏任務(wù) setTimeout2
          • 然后去查看宏任務(wù)隊(duì)列,宏任務(wù) setTimeout1 在 setTimeout2 之前,先執(zhí)行宏任務(wù) setTimeout1,輸出 setTimeout1
          • 在執(zhí)行宏任務(wù)setTimeout1時(shí)會生成微任務(wù)Promise2 ,放入微任務(wù)隊(duì)列中,接著先去清空微任務(wù)隊(duì)列中的所有任務(wù),輸出 Promise2
          • 清空完微任務(wù)隊(duì)列中的所有任務(wù)后,就又會去宏任務(wù)隊(duì)列取一個(gè),這回執(zhí)行的是 setTimeout2

          四、原型鏈與繼承

          1.原型和原型鏈

          原型:在JavaScript中原型是一個(gè)prototype對象,用于表示類型之間的關(guān)系。

          原型鏈:JavaScript萬物都是對象,對象和對象之間也有關(guān)系,并不是孤立存在的。對象之間的繼承關(guān)系,在JavaScript中是通過prototype對象指向父類對象,直到指向Object對象為止,這樣就形成了一個(gè)原型指向的鏈條,專業(yè)術(shù)語稱之為原型鏈。

          var Person=function() {
           this.age=18
           this.name='匿名'
          }
          var Student=function() {}
          //創(chuàng)建繼承關(guān)系,父類實(shí)例作為子類原型
          Student.prototype=new Person()
          var s1=new Student()
          console.log(s1)
          

          原型關(guān)系圖:

          當(dāng)試圖得到一個(gè)對象的某個(gè)屬性時(shí),如果這個(gè)對象本身沒有這個(gè)屬性,那么會去它的__proto__(即它的構(gòu)造函數(shù)的prototype)中尋找。如果一直找到最上層都沒有找到,那么就宣告失敗,返回undefined。最上層是什么 —— Object.prototype.__proto__===null

          2.繼承

          介紹幾種常見繼承方式(如需了解更多,請點(diǎn)擊JavaScript常見的六種繼承方式):

          • 原型鏈+借用構(gòu)造函數(shù)的組合繼承
          function Parent(value) {
           this.val=value
          }
          Parent.prototype.getValue=function() {
           console.log(this.val)
          }
          function Child(value) {
           Parent.call(this, value)
          }
          Child.prototype=new Parent()
          const child=new Child(1)
          child.getValue() // 1
          child instanceof Parent // true
          

          以上繼承的方式核心是在子類的構(gòu)造函數(shù)中通過 Parent.call(this) 繼承父類的屬性,然后改變子類的原型為 new Parent() 來繼承父類的函數(shù)。

          這種繼承方式優(yōu)點(diǎn)在于構(gòu)造函數(shù)可以傳參,不會與父類引用屬性共享,可以復(fù)用父類的函數(shù),但是也存在一個(gè)缺點(diǎn)就是在繼承父類函數(shù)的時(shí)候調(diào)用了父類構(gòu)造函數(shù),導(dǎo)致子類的原型上多了不需要的父類屬性,存在內(nèi)存上的浪費(fèi)。

          • 寄生組合繼承:這種繼承方式對上一種組合繼承進(jìn)行了優(yōu)化
          function Parent(value) {
           this.val=value
          }
          Parent.prototype.getValue=function() {
           console.log(this.val)
          }
          function Child(value) {
           Parent.call(this, value)
          }
          Child.prototype=Object.create(Parent.prototype, {
           constructor: {
           value: Child,
           enumerable: false,
           writable: true,
           configurable: true
           }
          })
          const child=new Child(1)
          child.getValue() // 1
          child instanceof Parent // true
          

          以上繼承實(shí)現(xiàn)的核心就是將父類的原型賦值給了子類,并且將構(gòu)造函數(shù)設(shè)置為子類,這樣既解決了無用的父類屬性問題,還能正確的找到子類的構(gòu)造函數(shù)。

          • ES6中class 的繼承

          ES6中引入了class關(guān)鍵字,class可以通過extends關(guān)鍵字實(shí)現(xiàn)繼承,還可以通過static關(guān)鍵字定義類的靜態(tài)方法,這比 ES5 的通過修改原型鏈實(shí)現(xiàn)繼承,要清晰和方便很多。需要注意的是,class關(guān)鍵字只是原型的語法糖,JavaScript繼承仍然是基于原型實(shí)現(xiàn)的

          class Parent {
           constructor(value) {
           this.val=value
           }
           getValue() {
           console.log(this.val)
           }
          }
          class Child extends Parent {
           constructor(value) {
           super(value)
           this.val=value
           }
          }
          let child=new Child(1)
          child.getValue() // 1
          child instanceof Parent // true
          

          class 實(shí)現(xiàn)繼承的核心在于使用 extends 表明繼承自哪個(gè)父類,并且在子類構(gòu)造函數(shù)中必須調(diào)用 super,因?yàn)檫@段代碼可以看成 Parent.call(this, value)。

          五、DOM操作與BOM操作

          1.DOM操作

          當(dāng)網(wǎng)頁被加載時(shí),瀏覽器會創(chuàng)建頁面的文檔對象模型(DOM),我們可以認(rèn)為 DOM 就是 JS 能識別的 HTML 結(jié)構(gòu),一個(gè)普通的 JS 對象或者數(shù)組。接下來我們介紹常見DOM操作:

          2.DOM事件模型和事件流

          DOM事件模型分為捕獲和冒泡。一個(gè)事件發(fā)生后,會在子元素和父元素之間傳播(propagation)。這種傳播分成三個(gè)階段。

          (1)捕獲階段:事件從window對象自上而下向目標(biāo)節(jié)點(diǎn)傳播的階段;

          (2)目標(biāo)階段:真正的目標(biāo)節(jié)點(diǎn)正在處理事件的階段;

          (3)冒泡階段:事件從目標(biāo)節(jié)點(diǎn)自下而上向window對象傳播的階段。

          DOM事件捕獲的具體流程

          捕獲是從上到下,事件先從window對象,然后再到document(對象),然后是html標(biāo)簽(通過document.documentElement獲取html標(biāo)簽),然后是body標(biāo)簽(通過document.body獲取body標(biāo)簽),然后按照普通的html結(jié)構(gòu)一層一層往下傳,最后到達(dá)目標(biāo)元素。

          接下來我們看個(gè)事件冒泡的例子:

          如何阻止冒泡?

          通過event.stopPropagation() 方法阻止事件冒泡到父元素,阻止任何父事件處理程序被執(zhí)行。 我們可以在上例中inner元素的click事件上,添加event.stopPropagation()這句話后,就阻止了父事件的執(zhí)行,最后只打印了'inner'。

           inner.onclick=function(ev) {
           console.log('inner')
           ev.stopPropagation()
          }
          

          3.事件代理(事件委托)

          由于事件會在冒泡階段向上傳播到父節(jié)點(diǎn),因此可以把子節(jié)點(diǎn)的監(jiān)聽函數(shù)定義在父節(jié)點(diǎn)上,由父節(jié)點(diǎn)的監(jiān)聽函數(shù)統(tǒng)一處理多個(gè)子元素的事件。這種方法叫做事件的代理。

          我們設(shè)定一種場景,如下代碼,一個(gè)<div>中包含了若干個(gè)<a>,而且還能繼續(xù)增加。那如何快捷方便地為所有<a>綁定事件呢?

          <div id="div1">
           <a href="#">a1</a>
           <a href="#">a2</a>
           <a href="#">a3</a>
           <a href="#">a4</a>
          </div>
          <button>點(diǎn)擊增加一個(gè) a 標(biāo)簽</button>
          

          如果給每個(gè)<a>標(biāo)簽一一都綁定一個(gè)事件,那對于內(nèi)存消耗是非常大的。借助事件代理,我們只需要給父容器div綁定方法即可,這樣不管點(diǎn)擊的是哪一個(gè)后代元素,都會根據(jù)冒泡傳播的傳遞機(jī)制,把父容器的click行為觸發(fā),然后把對應(yīng)的方法執(zhí)行,根據(jù)事件源,我們可以知道點(diǎn)擊的是誰,從而完成不同的事。

          最后,使用代理的優(yōu)點(diǎn)如下:

          • 使代碼簡潔
          • 減少瀏覽器的內(nèi)存占用

          4.BOM 操作

          BOM(瀏覽器對象模型)是瀏覽器本身的一些信息的設(shè)置和獲取,例如獲取瀏覽器的寬度、高度,設(shè)置讓瀏覽器跳轉(zhuǎn)到哪個(gè)地址。

          • window.screen對象:包含有關(guān)用戶屏幕的信息
          • window.location對象:用于獲得當(dāng)前頁面的地址(URL),并把瀏覽器重定向到新的頁面
          • window.history對象:瀏覽歷史的前進(jìn)后退等
          • window.navigator對象:常常用來獲取瀏覽器信息、是否移動(dòng)端訪問等等

          獲取屏幕的寬度和高度

          console.log(screen.width)
          console.log(screen.height)
          

          獲取網(wǎng)址、協(xié)議、path、參數(shù)、hash 等

          // 例如當(dāng)前網(wǎng)址是 https://juejin.im/timeline/frontend?a=10&b=10#some
          console.log(location.href) // https://juejin.im/timeline/frontend?a=10&b=10#some
          console.log(location.protocol) // https:
          console.log(location.pathname) // /timeline/frontend
          console.log(location.search) // ?a=10&b=10
          console.log(location.hash) // #some
          

          另外,還有調(diào)用瀏覽器的前進(jìn)、后退功能等

          history.back()
          history.forward()
          

          獲取瀏覽器特性(即俗稱的UA)然后識別客戶端,例如判斷是不是 Chrome 瀏覽器

          var ua=navigator.userAgent
          var isChrome=ua.indexOf('Chrome')
          console.log(isChrome)
          

          5.Ajax與跨域

          Ajax 是一種異步請求數(shù)據(jù)的一種技術(shù),對于改善用戶的體驗(yàn)和程序的性能很有幫助。 簡單地說,在不需要重新刷新頁面的情況下,Ajax 通過異步請求加載后臺數(shù)據(jù),并在網(wǎng)頁上呈現(xiàn)出來。常見運(yùn)用場景有表單驗(yàn)證是否登入成功、百度搜索下拉框提示和快遞單號查詢等等。Ajax的目的是提高用戶體驗(yàn),較少網(wǎng)絡(luò)數(shù)據(jù)的傳輸量

          如何手寫 XMLHttpRequest 不借助任何庫

          因?yàn)闉g覽器出于安全考慮,有同源策略。也就是說,如果協(xié)議、域名或者端口有一個(gè)不同就是跨域,Ajax 請求會失敗。

          那么是出于什么安全考慮才會引入這種機(jī)制呢? 其實(shí)主要是用來防止 CSRF 攻擊的。簡單點(diǎn)說,CSRF 攻擊是利用用戶的登錄態(tài)發(fā)起惡意請求。

          然后我們來考慮一個(gè)問題,請求跨域了,那么請求到底發(fā)出去沒有? 請求必然是發(fā)出去了,但是瀏覽器攔截了響應(yīng)。

          常見的幾種跨域解決方案:

          • JSONP:利用同源策略對<script>標(biāo)簽不受限制,不過只支持GET請求
          • CORS:實(shí)現(xiàn) CORS 通信的關(guān)鍵是后端,服務(wù)端設(shè)置 Access-Control-Allow-Origin 就可以開啟,備受推崇的跨域解決方案,比 JSONP 簡單許多
          • Node中間件代理或nginx反向代理:主要是通過同源策略對服務(wù)器不加限制

          6.存儲

          sessionStorage 、localStorage 和 cookie 之間的區(qū)別

          • 共同點(diǎn):都是保存在瀏覽器端,且都遵循同源策略。
          • 不同點(diǎn):在于生命周期與作用域的不同

          作用域:localStorage只要在相同的協(xié)議、相同的主機(jī)名、相同的端口下,就能讀取/修改到同一份localStorage數(shù)據(jù)。sessionStorage比localStorage更嚴(yán)苛一點(diǎn),除了協(xié)議、主機(jī)名、端口外,還要求在同一窗口(也就是瀏覽器的標(biāo)簽頁)下

          生命周期:localStorage 是持久化的本地存儲,存儲在其中的數(shù)據(jù)是永遠(yuǎn)不會過期的,使其消失的唯一辦法是手動(dòng)刪除;而 sessionStorage 是臨時(shí)性的本地存儲,它是會話級別的存儲,當(dāng)會話結(jié)束(頁面被關(guān)閉)時(shí),存儲內(nèi)容也隨之被釋放。

          六、模塊化

          幾種常見模塊化規(guī)范的簡介:

          CommonJS規(guī)范主要用于服務(wù)端編程,加載模塊是同步的,這并不適合在瀏覽器環(huán)境,因?yàn)橥揭馕吨枞虞d,瀏覽器資源是異步加載的,因此有了AMD CMD解決方案

          AMD規(guī)范在瀏覽器環(huán)境中異步加載模塊,而且可以并行加載多個(gè)模塊。不過,AMD規(guī)范開發(fā)成本高,代碼的閱讀和書寫比較困難,模塊定義方式的語義不順暢。

          CMD規(guī)范與AMD規(guī)范很相似,都用于瀏覽器編程,依賴就近,延遲執(zhí)行,可以很容易在Node.js中運(yùn)行。不過,依賴SPM 打包,模塊的加載邏輯偏重

          ES6 在語言標(biāo)準(zhǔn)的層面上,實(shí)現(xiàn)了模塊功能,而且實(shí)現(xiàn)得相當(dāng)簡單,完全可以取代 CommonJS 和 AMD 規(guī)范,成為瀏覽器和服務(wù)器通用的模塊解決方案

          avaScript有5種基本的數(shù)據(jù)類型,分別是:布爾、null、undefined、String和Number。這些基本類型在賦值的時(shí)候是通過值傳遞的方式。值得注意的是還有另外三種類型: Array、Function和Object,它們通過引用來傳遞。從底層技術(shù)上看,它們?nèi)际菍ο蟆?/p>

          基本數(shù)據(jù)類型

          如果一個(gè)基本的數(shù)據(jù)類型綁定到某個(gè)變量,我們可以認(rèn)為該變量包含這個(gè)基本數(shù)據(jù)類型的值。

          var x=10;
          var y='abc';
          var z=null;
          

          當(dāng)我們使用=將這些變量賦值到另外的變量,實(shí)際上是將對應(yīng)的值拷貝了一份,然后賦值給新的變量。我們把它稱作值傳遞

          var x=10;
          var y='abc';
          var a=x;
          var b=y;
          console.log(x, y, a, b) // 10, 'abc', 10, 'abc'
          

          a和x都包含10,b和y都包含'abc',并且它們是完全獨(dú)立的拷貝,互不干涉。如果我們將a的值改變,x不會受到影響。

          var x=10;
          var y='abc';
          var a=x;
          var b=y;
          a=5;
          b='def';
          console.log(x, y, a, b); // 10, 'abc', 5, 'def'
          

          對象

          如果一個(gè)變量綁定到一個(gè)非基本數(shù)據(jù)類型(Array, Function, Object),那么它只記錄了一個(gè)內(nèi)存地址,該地址存放了具體的數(shù)據(jù)。注意之前提到指向基本數(shù)據(jù)類型的變量相當(dāng)于包含了數(shù)據(jù),而現(xiàn)在指向非基本數(shù)據(jù)類型的變量本身是不包含數(shù)據(jù)的。

          對象在內(nèi)存中被創(chuàng)建,當(dāng)我們聲明arr=[],我們在內(nèi)存中創(chuàng)建了一個(gè)數(shù)組。arr記錄的是該內(nèi)存的地址。

          var arr=[]; // (a)
          arr.push(1); // (b)
          

          當(dāng)執(zhí)行完(a)之后,內(nèi)存中創(chuàng)建了一個(gè)空的數(shù)組對象,其內(nèi)存地址為#001, arr 指向該地址。

          當(dāng)執(zhí)行完(b)之后,數(shù)組對象中多了一個(gè)元素,但是數(shù)組的地址依然沒有變,arr也沒有變。

          引用傳遞

          對象是通過引用傳遞,而不是值傳遞。也就是說,變量賦值只會將地址傳遞過去。

          var reference=[1];
          var refCopy=reference;
          

          reference和refCopy指向同一個(gè)數(shù)組。 如果我們更新reference,refCopy也會受到影響。

          reference.push(2);
          console.log(reference, refCopy); // [1, 2], [1, 2]
          

          引用重新賦值

          如果我們將一個(gè)已經(jīng)賦值的變量重新賦值,那么它將包含新的數(shù)據(jù)或則引用地址。

          var obj={ first: 'fundebug.com'};
          obj={ second: 'fundebug.cn'};
          

          obj 從指向第一個(gè)對象變?yōu)橹赶虻诙€(gè)對象。

          如果一個(gè)對象沒有被任何變量指向,就如第一個(gè)對象(地址為#001),JavaScript引擎的垃圾回收機(jī)制會將該對象銷毀并釋放內(nèi)存。

          ==和===

          對于引用類型的變量,==和===只會判斷引用的地址是否相同,而不會判斷對象具體里屬性以及值是否相同。因此,如果兩個(gè)變量指向相同的對象,則返回true。

          var arrRef=['Hi!'];
          var arrRef2=arrRef;
          console.log(arrRef===arrRef2); // true
          

          如果是不同的對象,及時(shí)包含相同的屬性和值,也會返回false。

          var arr1=["Hi!"];
          var arr2=["Hi!"];
          console.log(arr1===arr2); // false
          

          如果想判斷兩個(gè)不同的對象是否真的相同,一個(gè)簡單的方法就是將它們轉(zhuǎn)換為字符串然后判斷。

          var arr1str=JSON.stringify(arr1);
          var arr2str=JSON.stringify(arr2);
          console.log(arr1str===arr2str); // true
          

          另一個(gè)方法就是遞歸地判斷每一個(gè)屬性的值,直到基本類型位置,然后判斷是否相同。

          函數(shù)參數(shù)

          當(dāng)我們將基本類型數(shù)據(jù)傳入函數(shù),函數(shù)會將這些數(shù)據(jù)拷貝賦值給函數(shù)的參數(shù)變量。

          var hundred=100;
          var two=2;
          function multiply(x, y) {
           return x * y;
          }
          var twoHundred=multiply(hundred, two);
          

          hundred的值拷貝給變量x,two的值拷貝給變量y。

          純函數(shù)

          對于一個(gè)函數(shù),給定一個(gè)輸入,返回一個(gè)唯一的輸出。除此之外,不會對外部環(huán)境產(chǎn)生任何附帶影響。我們機(jī)會稱該函數(shù)為純函數(shù)。所有函數(shù)內(nèi)部定義的變量在函數(shù)返回之后都被垃圾回收掉。

          但是,如果函數(shù)的輸入是對象(Array, Function, Object),那么傳入的是一個(gè)引用。對該變量的操作將會影響到原本的對象。這樣的編程手法將產(chǎn)生附帶影響,是的代碼的邏輯復(fù)雜和可讀性變低。

          因此,很多數(shù)組函數(shù),比如 Array.map 和 Array.filter 是以純函數(shù)的形式實(shí)現(xiàn)。雖然它們的參數(shù)是一個(gè)數(shù)組變量,但是通過深度拷貝并賦值給一個(gè)新的變量,然后在新的數(shù)組上操作,來防止原始數(shù)組被更改。

          我們來看一個(gè)例子:

          function changeAgeImpure(person) {
           person.age=25;
           return person;
          }
          var alex={
           name: 'Alex',
           age: 30
          };
          var changedAlex=changeAgeImpure(alex);
          console.log(alex); // { name: 'Alex', age: 25 }
          console.log(changedAlex); // { name: 'Alex', age: 25 }
          

          在非純函數(shù)changeAgeImpure中,將對象person的age更新并返回。原始的alex對象也被影響,age更新為25。

          讓我們來看如何實(shí)現(xiàn)一個(gè)純函數(shù):

          function changeAgePure(person) {
           var newPersonObj=JSON.parse(JSON.stringify(person));
           newPersonObj.age=25;
           return newPersonObj;
          }
          var alex={
           name: 'Alex',
           age: 30
          };
          var alexChanged=changeAgePure(alex);
          console.log(alex); // { name: 'Alex', age: 30 }
          console.log(alexChanged); // { name: 'Alex', age: 25 }
          

          我們通過JSON.sringify將對象變?yōu)橐粋€(gè)字符串,然后再通過JSON.parse將字符串變回對象。通過該操作會生成一個(gè)新的對象。

          一道簡單的面試題

          值傳遞和引用傳遞經(jīng)常在面試中被問到,來嘗試回答一下如下代碼如何輸出:

          一陣子利用Balazor開發(fā)了一個(gè)NuGet站點(diǎn),對WebAssembly進(jìn)行了初步的了解,覺得挺有意思。在接下來的一系列文章中,我們將通過實(shí)例演示的方式介紹WebAssembly的一些基本概念和編程模式。首先我們先來說說什么是WebAssembly,它主要幫助我們解決什么問題。

          本文演示實(shí)例下載:app1 app2

          一、概述
          二、WebAssembly程序(app.wat)
          三、編譯生成app.wasm
          四、JavaScript程序
          五、跑起來看看
          六、調(diào)用導(dǎo)出函數(shù)

          一、概述

          WebAssembly可以視為一種采用精簡的”二進(jìn)制格式”的“低等級”、“類匯編”語言。目前主流的瀏覽器均提供了對WebAssembly的支持,雖然WebAssembly的執(zhí)行性能(它能夠提供near-native的執(zhí)行性能)是JavaScript無法比擬的,但是在表達(dá)能力和靈活性還是不如JavaScript,所以WebAssembly的出現(xiàn)并不是要取代JavaScript,而是作為JavaScript的“助手”,兩者配合,各自發(fā)揮自身的優(yōu)勢,進(jìn)而開發(fā)出更高質(zhì)量的Web應(yīng)用。

          與其說WebAssembly像匯編,不如說它更像MSIL,因?yàn)樗皇敲嫦蜷_發(fā)者的高級語言,而是高級語言的“編譯目標(biāo)”。雖然.NET 的運(yùn)行時(shí)CLR不能直接執(zhí)行C#、VB.NET和F#這樣的高級語言編寫的代碼,而執(zhí)行執(zhí)行MSIL指令,但是我們可以將前者編譯成后者。與之類似,我們也可以使用C、C++、C#、Rust和AssemblyScript(面向WebAssembly的TypeScript)作為開發(fā)語言,然后將代碼編譯成WebAssembly。

          與匯編一樣,雖然WebAssembly采用的是單純的二進(jìn)制格式,但是可以轉(zhuǎn)換成文本形式。不僅如此,我們還可以按照這樣的文本格式來編寫程序(在接下來的內(nèi)容中,我們提供的WebAssembly程序均采用這樣的編寫方式),并利用相應(yīng)的工具將其編程成WebAssembly。WebAssembly文本采用一種名為S-expression的樹形結(jié)構(gòu),我們定義的內(nèi)容都存在與一個(gè)具體的“節(jié)點(diǎn)”中,每個(gè)節(jié)點(diǎn)通過小括號包裹起來,子節(jié)點(diǎn)直接內(nèi)嵌于父節(jié)點(diǎn)中。由于模塊是WebAssembly的基本部署和加載單元,所以module總是它們的根節(jié)點(diǎn),如下所示的就是一個(gè)合法的最簡單的WebAssembly程序。

          (module)

          為了讓大家對WebAssembly文本的S-expression有一個(gè)大致的了解,我們采用這樣的形勢定義了一個(gè)進(jìn)行整數(shù)加法運(yùn)算的add函數(shù)。如下面的代碼片段所示,我們定義的函數(shù)通過內(nèi)嵌于module節(jié)點(diǎn)的(func)節(jié)點(diǎn)表示。它具有一些子節(jié)點(diǎn),其中(export "add") 表示將這個(gè)函數(shù)以名稱“add”進(jìn)行導(dǎo)出,這意味著加載此模塊的JavaScript應(yīng)用可以直接調(diào)用此函數(shù)。額外三個(gè)節(jié)點(diǎn)(param $x i32) (param $y i32) (result i32)定義了函數(shù)的簽名,揭示了此函數(shù)包含兩個(gè)Int32(i32)的輸入?yún)?shù),返回值(結(jié)果)的類型也是Int32(i32)。為了提供可讀性,同時(shí)也方面?zhèn)鲄⒎矫妫覀儗?shù)進(jìn)行了命名($x和$y)

          (module
          (func (export "add") (param $x i32) (param $y i32) (result i32)
          local.get $x
          local.get $y
          i32.add)
          )

          值得一提的是,WebAssembly僅僅支持4種數(shù)據(jù)類型,分別是32和64位的整數(shù)(i32和i64)和浮點(diǎn)數(shù)(f32和f64),連我們最常使用的字符串類型都不支持。由于字符串本質(zhì)上就是一組字符序列,而一個(gè)字符本質(zhì)上是采用某種編碼的一段字節(jié)序列,所以WebAssembly中針對字符串的處理需要自行解決編解碼的工作,所以直接以S-expression文本的形式編寫WebAssembly程序并不容易。

          上面說WebAssembly更像MSIL,還因?yàn)閳?zhí)行WebAssembly的VM是一個(gè)“堆棧機(jī)(Stack Machine)”,意味著它采用“壓棧”的方式傳遞參數(shù)。而匯編面向的是真正的機(jī)器語言,是“堆棧機(jī)(Stack Machine)”和“寄存器機(jī)(Register Machine)”的結(jié)合,不僅參數(shù)傳遞可以采用堆棧,也可以采用寄存器。這一點(diǎn)可以從add函數(shù)的實(shí)現(xiàn)看出來,由于最終需要執(zhí)行i32.add指令,我們需要在這之前調(diào)用local.get指令將兩個(gè)參數(shù)壓入棧中。i32.add執(zhí)行后的結(jié)果也將入棧,并成為函數(shù)返回值。

          在對WebAssembly以及基于S-expression的文本形式有了基本了解之后,我們通過一個(gè)簡單的例子來演示一下一個(gè)WebAssembly程序大體上如何編寫,我們著重關(guān)注JavaScript應(yīng)用和WebAssembly之間的功能交互。我們創(chuàng)建一個(gè)空的目錄,并創(chuàng)建兩個(gè)文本文件app.wat和index.html,前者代表以S-expression文本形式編寫的WebAssembly程序,后者是一個(gè)空的HTML頁面,我們利用它提供的JavaScript程序加載并執(zhí)行編譯后的WebAssembly模塊。

          二、WebAssembly程序(app.wat)

          如下所示的是app.wat的內(nèi)容,看起來很長,好在我們現(xiàn)在對S-expression有了基本的了解,所以理解起來沒有什么問題。模塊的第一個(gè)節(jié)點(diǎn)(func $print (import "js" "print") (param $op1 i32) (param $op2 i32) (param $op i32) (param $result i32))同樣是定義了一個(gè)函數(shù),我們將其命名為$print, 子節(jié)點(diǎn)(import "js" "print") 表明該函數(shù)是從作為宿主的Javascript應(yīng)用中導(dǎo)入的,具體的路徑為js->print。如果導(dǎo)入的Javascript對象通過變量imports表示,意味著imports.js.print表示的就是這個(gè)導(dǎo)入的對象。至于其余的四個(gè)節(jié)點(diǎn)(param $op1 i32) (param $op2 i32) (param $op i32) (param $result i32))同樣是提供了函數(shù)的簽名:個(gè)i32類型的參數(shù),分別表示第一個(gè)操作數(shù)、第二個(gè)操作數(shù)、操作符(1,2,3,4分別代表加、減、乘、除)和執(zhí)行結(jié)果。由于這個(gè)函數(shù)僅僅打印運(yùn)算表達(dá)式,所以沒有返回值。


          (module

          ;; import js func print (op1, op2, op, result)

          (func $print (import "js" "print")
          (param $op1 i32)
          (param $op2 i32)
          (param $op i32)
          (param $result i32))

          ;; int32 add (int32 x, int32 y)
          (func $add (param $x i32) (param $y i32) (result i32)
          local.get $x
          local.get $y
          i32.add)

          ;; int32 sub(int32 x, int32 y)
          (func $sub (param $x i32) (param $y i32) (result i32)
          local.get $x
          local.get $y
          i32.sub)


          ;; int32 mul(int32 x, int32 y)
          (func $mul (param $x i32) (param $y i32) (result i32)
          local.get $x
          local.get $y
          i32.mul)


          ;; int32 div(int32 x, int32 y)
          (func $div (param $x i32) (param $y i32) (result i32)
          local.get $x
          local.get $y
          i32.div_u)

          (func $main
          ;; call print(1, 2, add(1,2), 1)
          i32.const 1
          i32.const 2
          i32.const 1

          i32.const 1
          i32.const 2
          call $add
          call $print

          ;; call print(1, 2, sub(1,2), 2)
          i32.const 1
          i32.const 2
          i32.const 2

          i32.const 1
          i32.const 2
          call $sub
          call $print

          ;; call print(1, 2, mul(1,2), 3)
          i32.const 1
          i32.const 2
          i32.const 3

          i32.const 1
          i32.const 2
          call $mul
          call $print

          ;; call print(1, 2, div(1,2), 4)
          i32.const 1
          i32.const 2
          i32.const 4

          i32.const 1
          i32.const 2
          call $div
          call $print
          )

          (start $main)
          )

          接下來我們定義了四個(gè)進(jìn)行加、減、乘和除運(yùn)算的函數(shù)add、sub、mul和div,它們與上面定義的add函數(shù)類似,不過由于缺少了(export “{funcname}”)節(jié)點(diǎn),所以它們僅僅是四個(gè)內(nèi)部函數(shù)而已。接下來,我們定義了一個(gè)$main函數(shù),它會傳入相同的參數(shù)(1、2)調(diào)用上述4個(gè)函數(shù),并調(diào)用導(dǎo)入的print方法將包含結(jié)果的運(yùn)算表達(dá)式打印出來。雖然命名為$main,但是它也僅僅是一個(gè)普通的函數(shù)而已,所以我們需要利用(start $main)節(jié)點(diǎn)將其作為入口函數(shù),這樣它就會在加載的時(shí)候自動(dòng)執(zhí)行了。

          三、編譯生成app.wasm

          以文本形式編寫的WebAssembly程序需要編譯成二進(jìn)制模塊才能被加載執(zhí)行,這里我們使用的wat2wasm這個(gè)工具,這個(gè)工具可以從這里下載。除了將wat文件轉(zhuǎn)化成wasm文件的wat2wasm,下載包里還包含了其他一些有用的工具,比如進(jìn)行反向操作的wasm2wat。上面編寫的app.wat文件利用如下的命令就可以編譯生成WebAssembly目標(biāo)文件app.wasm。

          wat2wasm app.wat -o app.wasm

          四、JavaScript程序

          如下所示的就是index.html文件的內(nèi)容,我們著重關(guān)注其提供的JavaScript代碼。我們首先定義被WebAssembly導(dǎo)入的用來輸出運(yùn)行表達(dá)式的函數(shù)print,然后按照導(dǎo)入路徑j(luò)s.print將其封裝到一個(gè)導(dǎo)入對象中({"js":{"print":print}})。我們調(diào)用WebAssembly.instantiateStreaming函數(shù)以異步方式加載app.wasm模塊,并創(chuàng)建對應(yīng)的實(shí)例。具體下載app.wasm模塊通過第一個(gè)參數(shù)提供的Promise(fetch("app.wasm"))完成,第二個(gè)參數(shù)代表導(dǎo)入對象。

          <!DOCTYPE html>
          <html>
          <head></head>
          <body>
          <script>
          var url="app.wasm";
          var print=(op1, op2, op, result)=> {
          switch (op) {
          case 1:
          console.log(`${op1} + ${op2}=${result}`);
          break;
          case 2:
          console.log(`${op1} - ${op2}=${result}`);
          break;
          case 3:
          console.log(`${op1} * ${op2}=${result}`);
          break;
          case 4:
          console.log(`${op1} / ${op2}=${result}`);
          break;
          default:
          console.log("invalid operator.");
          }
          };
          WebAssembly
          .instantiateStreaming(fetch("app.wasm"),
          {"js":{"print":print}});
          </script>
          </body>
          </html>

          五、跑起來看看

          我們采用相應(yīng)的方式將當(dāng)前目錄發(fā)布為本地web站點(diǎn),比如執(zhí)行Python命令(python -m http.server),然后我們利用瀏覽器訪問此站點(diǎn)的默認(rèn)文件index.html,瀏覽器的控制臺輸出就會看到WebAssembly模塊初始化輸出的四個(gè)運(yùn)算表達(dá)式。

          六、調(diào)用導(dǎo)出函數(shù)

          上面我們演示了WebAssembly模塊在初始化的時(shí)候調(diào)用導(dǎo)入的JavaScript函數(shù),現(xiàn)在我們來演示JavaScript應(yīng)用如何調(diào)用從WebAssembly導(dǎo)出的函數(shù),為此我們將app.wat的代碼改寫成如下的形式,僅僅保留4個(gè)導(dǎo)出的函數(shù)add、sub、mul和div。

          (module
          (func (export "add") (param $x i32) (param $y i32) (result i32)
          local.get $x
          local.get $y
          i32.add)

          (func (export "sub") (param $x i32) (param $y i32) (result i32)
          local.get $x
          local.get $y
          i32.sub)

          (func (export "mul") (param $x i32) (param $y i32) (result i32)
          local.get $x
          local.get $y
          i32.mul)

          (func (export "div") (param $x i32) (param $y i32) (result i32)
          local.get $x
          local.get $y
          i32.div_u)
          )

          index.html中的Javascript代碼也做了如下的修改:我們在調(diào)用WebAssembly.instantiateStreaming函數(shù)成功加載WebAssembly模塊并創(chuàng)建對應(yīng)模塊實(shí)例后,利用返回結(jié)果的instance屬性得到這個(gè)模塊實(shí)例。模塊導(dǎo)出的成員都保存在該實(shí)例的exports屬性返回的集合中,為此我們從中提取出導(dǎo)出的四個(gè)返回,并利用它們完成對應(yīng)的運(yùn)算后,調(diào)用console.log函數(shù)將包含結(jié)果的運(yùn)算表達(dá)式輸出到控制臺上。由于WebAssembly模塊不在需要調(diào)用導(dǎo)入的函數(shù),所以調(diào)用instantiateStreaming函數(shù)的時(shí)候不需要在指定導(dǎo)入對象。

          <!DOCTYPE html>
          <html>
          <head></head>
          <body>
          <script>
          var url="app.wasm";
          WebAssembly
          .instantiateStreaming(fetch("app.wasm"))
          .then(result=> {
          var exports=result.instance.exports;

          var result=exports.add(1,2);
          console.log(`1 + 2=${result}`);

          result=exports.sub(1,2);
          console.log(`1 - 2=${result}`);

          result=exports.mul(1,2);
          console.log(`1 * 2=${result}`);

          result=exports.div(1,2);
          console.log(`1 / 2=${result}`);
          });
          </script>
          </body>
          </html>

          程序運(yùn)行之后,瀏覽器的控制臺上依然會輸出相同的結(jié)果。


          主站蜘蛛池模板: 国产AV午夜精品一区二区三| 精品国产一区二区三区香蕉事| 国产在线观看91精品一区| 亚洲综合在线成人一区| 免费一本色道久久一区| 日本精品一区二区在线播放 | 国产高清在线精品一区二区| 国产vr一区二区在线观看| 无码人妻一区二区三区免费手机 | 国产丝袜一区二区三区在线观看| 三上悠亚精品一区二区久久| 亚洲一区二区在线免费观看| 人妻无码视频一区二区三区| 国产一区二区三区影院| 日韩视频一区二区在线观看| 久久se精品动漫一区二区三区| 天堂Aⅴ无码一区二区三区| 亚洲日韩中文字幕一区| 一区二区三区在线视频播放| 香蕉久久av一区二区三区| 天堂Aⅴ无码一区二区三区| 日本一区二区三区免费高清在线| 天堂一区二区三区精品| 无码aⅴ精品一区二区三区| 在线精品自拍亚洲第一区| 亚洲国产激情在线一区| 亚洲国产老鸭窝一区二区三区| 日韩av无码一区二区三区| 一区二区不卡视频在线观看| 精品国产一区二区三区av片| 国产福利电影一区二区三区,亚洲国模精品一区 | 色窝窝免费一区二区三区| 国精产品999一区二区三区有限| 国产成人av一区二区三区在线| 国产精品特级毛片一区二区三区| 精品久久久久一区二区三区| 精品人妻一区二区三区四区在线 | 精品视频一区二区三区在线播放| 无码人妻久久久一区二区三区 | 久久AAAA片一区二区| 日本免费一区二区三区|