淺談JavaScript

最近工作中接觸到JavaScript相關項目,作為一名JS初學者,通過項目與學習獲得了JS的一些知識,借著這篇文章,分享一下這段時間了解到的內(nèi)容。

在開始之前,讓我們先思考以下幾個問題:

  • JavaScript以Java為前綴,兩種編程語言之間有什么關聯(lián)?
  • JS能夠幫助前端開發(fā)者解決什么問題?
  • 市場上有各種各樣的瀏覽器,怎么能夠保證同一段JS代碼在不同瀏覽器都可以執(zhí)行成功呢?
  • JS能夠操作網(wǎng)頁內(nèi)容,開發(fā)者是否可以通過惡意JS代碼入侵用戶電腦?
  • 在Java中,需要定義數(shù)據(jù)類型,而在JS中卻不用顯式申明變量數(shù)據(jù)類型,在這背后的原理是什么?
  • JS中關鍵字Class與Java中的Class含義是相同的么?
  • 在JS中經(jīng)常用到的回調(diào)函數(shù)是什么?作用是什么?

本文分為以下兩部分,圍繞上面這些問題展開描述;

  • 發(fā)展背景
  • 語言特性(包含基礎語法以及JS特性)

發(fā)展背景

JavaScript的產(chǎn)生依賴于瀏覽器的發(fā)展,讓我們把時間撥回到1993年,第一個世界公認的瀏覽器在美國NCSA誕生了,名稱為Mosaic,當時受到了人們的歡迎;然而1994年的時候,Mosaic的競爭對手Netscape出現(xiàn)了,Netscape開發(fā)的瀏覽器取其名為Mozilla(Mosaic killer的簡稱),并且為了能夠攻破Mosaic的壟斷地位,首先提出了通過腳本語言操作網(wǎng)頁元素的想法,這樣使網(wǎng)頁看起來更生動流暢,創(chuàng)新理念及新技術的應用使得Mozilla越來越受歡迎;至于這種腳本語言為什么被命名為JavaScript(以下簡稱JS),該名稱也是由于Netscape和Sun公司合作后,當時Java是被廣大開發(fā)者和市場熟知的,為了博得市場的歡迎,名稱中引入了Java前綴;在1995年Netscape完成了JavaScript的初版,Netscape是開創(chuàng)者,一石激起千層浪,互聯(lián)網(wǎng)巨頭微軟為了能夠贏得市場上瀏覽器的份額,緊隨Netscape的腳步,開發(fā)了可以在 Internet explorer瀏覽器運行的腳本語言取名為JScript,再加上CEnvi(Nombas公司發(fā)明的) 中的 ScriptEase,至此三個版本的Script互相競爭,長期來看,這三種版本Script的語法規(guī)范與運行環(huán)境有差異,帶來的后果是同一腳本在不同瀏覽器上表現(xiàn)不一致,開發(fā)者需要依賴于瀏覽器的運行環(huán)境定制腳本,廣大開發(fā)者及瀏覽器用戶難以忍受這種現(xiàn)狀,那接下來發(fā)生了什么事情呢?

經(jīng)過了野蠻生長的瀏覽器腳本語言,Netscape想通過統(tǒng)一JS規(guī)范結(jié)束上述困境,在歐洲計算機制造商協(xié)(ECMA European Computer Manufactures Association)的幫助下,定制了JS腳本語言規(guī)范,1997年的時候確立了JavaScript標準,命名為ECMAScript(以下簡稱ES),這個標準的作用在于它明確了一種編程語言的特性、語法、類型、功能和 API 等實現(xiàn)標準,那么對于各家瀏覽器廠商而言,在支持ES規(guī)范的基礎上,可以選擇各自的實現(xiàn)方式,比如Chrome中的V8(JS)、Firefox中的SpiderMonkey(C++/C)以及IE的Chakra(JScript)。對開發(fā)者的好處是,統(tǒng)一了平臺兼容問題,只需要按照ECMAScript中的標準來實現(xiàn)需求,不需要考慮瀏覽器間的兼容問題。

在前人的不懈努力下,至此ES統(tǒng)一了JavaScript的標準,2000年之后JS的發(fā)展有三個變革點:

  • Ajax(Asynchronous JavaScript And XML) - 異步的與服務器交互,動態(tài)的修改頁面元素;
  • Node - 告別了JS只能作用于前端的時代,將JS引入了服務器后端領域;
  • ES6 - 2015年ES6的推出,給JS帶來了新的特性,更加適合熟悉傳統(tǒng)編程語言的開發(fā)者,奠定了Morden Java Script的基礎。

wiki上可以看到,二十多年來ECMAScript在不斷的完善和成長,現(xiàn)如今,ES6進入了廣大開發(fā)者的視野,JS成為了近幾年最受歡迎的編程語言,在這曲折而又充滿驚喜的發(fā)展道路上,啟發(fā)我們的不僅僅是新技術的涌現(xiàn)與實踐,更多的是敢于創(chuàng)新的精神值得去學習。

這一節(jié)有三點需要說明一下:

  • JS設計初衷是為了讓頁面更生動,可以動態(tài)操作網(wǎng)頁元素,JS不能夠獨立發(fā)揮作用,需要結(jié)合BOM(Browser Object Model)和DOM(Document Object Model)對象,感興趣的查看一下這三者的關系;
  • ES定義了JS的語言規(guī)范,隨著技術的不斷改進,ES的標準也在不斷的更新,在這里可以看到最新的規(guī)范;
  • JS不僅可以運行在瀏覽器端的JS引擎中,還可以運行在裝有Node環(huán)境的機器上,在后端服務器中也看到JS的身影。

這張圖可以幫助大家更好的了解JS的發(fā)展歷史:


JS發(fā)展歷史

相信大家對JS發(fā)展路徑有了初步的認識,接下來聊一下JS這門語言特性。

JavaScript語言特性

首先我們來看一下來自MDN對JS的定義:

JavaScript? (often shortened to JS) is a lightweight, interpreted, object-oriented language with first-class functions, and is best known as the scripting language for Web pages, but it's used in many non-browser environments as well. It is a prototype-based, multi-paradigm scripting language that is dynamic, and supports object-oriented, imperative, and functional programming styles.

下面我們理解一下這四個關鍵字:

  1. 輕量級(lightweight) - 理解為這門腳本為弱類型語言,上手容易,功能強大;
  2. 解釋型(Interpreted) - 作為腳本語言,只需要JS引擎解釋運行,沒有編譯步驟;
  3. 面向?qū)ο螅╫bject-oriented language with first-class functions)- 相對于面向過程,具備抽象封裝、繼承、多態(tài)的特征,其中包含頭等函數(shù),意味著函數(shù)可以作為函數(shù)的參數(shù)、函數(shù)的返回值,可以賦值給變量或存儲在數(shù)據(jù)結(jié)構中,包含了函數(shù)式編程的思維;
  4. 基于原型(prototype-based)- 簡單可以理解為基于實例的編程,沒有Java中類的概念,像Java屬于基于類編程,感興趣的可以參考一下wiki,在MDN有比較兩者的區(qū)別。

JS可以幫助開發(fā)者做哪些事情呢?

其實在瀏覽器端JS活動范圍是有限的,它不能訪問CPU、內(nèi)存等底層系統(tǒng),所以是一門用戶安全的編程語言,不存在有惡意腳本攻擊用戶電腦的情況。在活動范圍內(nèi),JS大概可以做下面三類事情:

  • 調(diào)用DOM接口,修改網(wǎng)頁元素,為網(wǎng)頁添加豐富的動態(tài)功能,為用戶提供流暢美觀的網(wǎng)頁;
  • 用戶交互,響應用戶的行為,比如點擊按鈕、輸入字符串、鼠標移動等事件;
  • 處理前端用戶請求,與服務器交互,異步請求服務器資源。

到現(xiàn)在為止,以上是關于JS的特性和作用描述,下面通過常用數(shù)據(jù)類型、事件和函數(shù)、異步特性這三方面幫助大家進一步認識JS。

常用數(shù)據(jù)類型

與大部分編程語言類似,JS支持基本數(shù)據(jù)類型和引用數(shù)據(jù)類型,兩者的區(qū)別在于值是否存在于堆區(qū)(heap),如下圖所示:

JS內(nèi)存分配模型

可以看到JS內(nèi)存空間分配有兩部分組成:

  • 棧區(qū)(stack) - 有序排列,大小固定
  • 堆區(qū)(heap) - 無序排列,大小不固定

其中對于基本數(shù)據(jù)類型,變量地址和值都會放到棧區(qū),比如let num = 1這句申明,變量名num會與棧區(qū)分配的地址綁定,并且num的值1也會放到棧區(qū)中,這樣做的好處也是為了方便存儲和提升訪問效率,JS為開發(fā)者提供了7種基本數(shù)據(jù)類型:string、number、bigint、 boolean、symbol、null、underfined;

基本數(shù)據(jù)類型不能滿足開發(fā)者的所有需要,更多的時候需要操作對象,也就是引用類型,先看一個例子,比如let array=[1,2],首先我們申明了一個array變量,這個變量與棧區(qū)(stack)綁定的地址存放的是一個指針,這個指針是堆區(qū)(heap)的地址,具體array的值存在堆區(qū)中,意味著引用類型的棧區(qū)(stack)存放了指向堆區(qū)(heap)的內(nèi)存地址,引用類型的值存放在堆區(qū)空間中,常用的引用類型包含:Object、Function、Array等。

這兩種不同的存儲方式影響到變量的初始化、賦值和數(shù)據(jù)傳遞:

  1. 數(shù)據(jù)初始化

JS中初始化數(shù)據(jù)不需要指定數(shù)據(jù)類型,比如同樣定義一個字符串類型的變量:

//Java
String s = "Java";
//JS
let s = 'javascript';

原因在于JS是在被解析器執(zhí)行階段才能分析確定數(shù)據(jù)類型,意味著JS是一門弱類型語言,在編寫代碼定義變量階段不需要申明類型;

var str = "hello world";//String
let n = 123; //Number
let b = true; //Boolean

在ES6之前,只需要一個var關鍵字即可,由于var關鍵字的作用域是function級別的,不支持block級別(只要代碼中一對大括號{},意味著是一個block),會導致以下變量污染問題:

var myVar = 1;
if (true) {
  var myVar = 2;
  console.log(myVar);
}
console.log(myVar); //Output 2, Expected 1 

在ES6中除了支持var,還支持以下let、const關鍵字:

JS支持的關鍵字

這三者的區(qū)別在于作用域和值是否可被修改,let和const都是block級別的,執(zhí)行上面同樣的代碼:

let myVar = 1;
if (true) {
   let myVar = 2;
  console.log(myVar);
}
console.log(myVar); //Output 1, Expected 1 

再看一下變量的可修改性,其中var和let是可被多次修改賦值,const申明的是常量,注意如果const申明的是一個引用類型,對象內(nèi)的屬性仍然是可以被修改,比如下面的代碼可以正常運行:

const ANIMAL = {name : "cat"};
console.log(ANIMAL.name); //Output cat
ANIMAL.name = "dog";
console.log(ANIMAL.name); //Output dog

對于基本數(shù)據(jù)類型,初始化變量在棧區(qū)完成;對于引用數(shù)據(jù)類型,初始化變量需要在棧區(qū)和堆區(qū)完成。

  1. 數(shù)據(jù)賦值

數(shù)據(jù)初始化完成后,經(jīng)常會被別的變量引用,比如現(xiàn)在有A、B兩個變量,A變量已經(jīng)完成了初始化,B=A完成了將A賦值給B的過程,問題在于B值的變化會引起A的變化么?搞清楚這個問題有助于編寫正確的代碼,針對基本數(shù)據(jù)類型賦值,數(shù)據(jù)初始化只在棧區(qū)完成,簡單理解A對B的賦值,其實是內(nèi)存中會產(chǎn)生A的一個副本,然后和B的變量綁定到一起,結(jié)果就是A和B是兩個獨立的內(nèi)存地址,兩者值的變化互不干擾,來看看下面的例子:

let num1 = 123;
let num2 = num1;
num2 = 345;
console.log(num1); // output 123
console.log(num2);// output 345

從測試結(jié)果來看,也是符合預期的,盡管將num1賦值給了num2,num2的改變并不會影響num1的值;

引用數(shù)據(jù)類型的結(jié)果和上面是一樣的么?接下來我們看一下引用數(shù)據(jù)類型的變量賦值:

let obj1 = new Object();
obj1.name = 'obj1 value';
let obj2 = obj1;
obj1.name = 'obj2 value';
console.log(obj1.name); // output obj2.value
console.log(obj2.name); // output obj2 value

為什么obj2值的改變也會帶動obj1值的變化呢?這是由于當把obj1賦值給obj2的時候,只是在??臻g內(nèi)復制了一份指向堆空間的地址賦值給obj2,結(jié)果obj1和obj2的指針指向的是堆區(qū)中同一片區(qū)域,所以obj2的變化會改變obj1的值。

  1. 數(shù)據(jù)傳遞

JS中所有函數(shù)的參數(shù)都是按值傳遞的,當把函數(shù)外部的值賦給函數(shù)內(nèi)部參數(shù)時,類似于上面提到的值從一個變量賦值給另一個變量,意味著如果是基本數(shù)據(jù)類型,則會復制一份??臻g內(nèi)的數(shù)據(jù)給函數(shù)內(nèi)部用,兩者隔離互不影響;如果是引用類型,則會復制一份棧空間內(nèi)的堆空間地址給函數(shù)內(nèi)部用,函數(shù)內(nèi)部值的變化會影響函數(shù)外值的變化,從下面的測試結(jié)果來看也是符合這個結(jié)論的:

//基本數(shù)據(jù)傳遞
function changeValue(num1){
 num1 = 345;
 return num1;
}
let num1 = 123;
let num2 = changeValue(num1);
console.log(num1); // output 123
console.log(num2); // output 345

//引用數(shù)據(jù)傳遞
function changeName(obj){
 obj.name = 'obj2 value';
 return obj;
}
let obj1 = {name:"obj1 value"};
let obj2 = changeName(obj1);
console.log(obj1.name); // output 'obj2 value'
console.log(obj2.name); // output 'obj2 value'

上述是從內(nèi)存分配角度介紹了JS中的數(shù)據(jù)類型,核心在于基本數(shù)據(jù)類型和引用數(shù)據(jù)類型的存儲方式不同會影響變量的操作,針對JS中常見數(shù)據(jù)類型的語法可以參考官方文檔,接下來有必要聊一下JS中的Object對象;

Object

對象是JS中重要的概念,上一節(jié)已經(jīng)提到Object是引用數(shù)據(jù)類型,相比較于其他基本數(shù)據(jù)類型(僅存儲單一的值比如字符串、數(shù)字等),Object可以用來存儲和操作復雜的數(shù)據(jù),通過下面的語句先來創(chuàng)建一個對象:

//Method 1 "object literal" syntax   支持一次創(chuàng)建一個對象
let user = {};  

//Method 2  "object constructor" syntax  支持代碼重用性
let user = new Object(); 

function User(name) {
 this.name = name;
 this.isAdmin = false;
}
let user = new User("Jack");

user可以理解為一個存儲在棧區(qū)的指針,對應的值存儲在堆區(qū),值是包含多個屬性的集合,每一個屬性包含屬性名稱(key)和屬性值(Value),對于存儲在堆區(qū)的數(shù)值可以理解為多組鍵值對(key-value pairs),其中key可以必須為字符串或者symbols,value可以是任意類型,接著給user添加一些屬性:

let user = {     // an object
  name: "John",  // by key "name" store value "John"
  age: 30        // by key "age" store value 30
};

我們可以通過objectname.propertyname或者objectname["propertyname"]訪問屬性值,而且可以操作(增加、刪除)對象屬性,比如delete user.name將刪除user中的name屬性。

JS中Array、Date、Map、Function等類型都是Object,通過instanceof可以判斷對象是否屬于某個class:

//Array
let myArray = [1,2,3,4]
console.log(typeof(myArray))  // output :object
console.log(myArray instanceof Array) // output: true

//Map
let myMap = new Map();
myMap.set('1', 'str1');   // a string key
console.log(typeof(myMap)) // output :object
console.log(myMap instanceof Map) // output: true

//Set
let mySet = new Set();
let john = { name: "John" };
mySet.add(john);
console.log(typeof(mySet));  // output :object
console.log(mySet instanceof Set) // output: true

之前我們提到JS是基于原型的,為什么這里出現(xiàn)Map、Set等Class?
通過下面的例子來了解一下Class關鍵字:

//Custom Class
class Animal {
  constructor(name) {
    this.name = name;
  }
    
  printName() {
    console.log(this.name);
  }
}  
let myAnimal = new Animal
console.log(typeof Animal);  // Output: function
console.log(typeof myAnimal);  // Output: object
console.log(myAnimal instanceof Animal) // Output: true

其中的constructor方法會被new關鍵字調(diào)用,通過上面的輸出可以看到Animal的類型是function,其實當我們通過class申明時,以下兩點需要了解:

  1. 通過Class申明的Animal,內(nèi)部其實是以function申明的方式實現(xiàn)(typeof Animal == function),contructor中的代碼邏輯就是function的方法體:
// Define the Animal by function keyword
function Animal(name) {
  this.name = name;
}
      
Animal.prototype.printName = function() {
  console.log(this.name);
}
let duck = new Animal('duck');
duck.printName();  // Displays "duck"  

//Define the Animal by class key word
class Animal {
  constructor(name) {
    this.name = name;
  }
    
  printName() {
    console.log(this.name);
  }
}  
console.log(typeof Animal);  // Display "function"
  1. Class中的方法,其實就是Animal.prototype.method,意味著通過class關鍵字申明類與function申明的類,本質(zhì)是沒有區(qū)別的。

使用Class關鍵字有什么好處呢?

  • JS提供的語法糖,更符合傳統(tǒng)后端開發(fā)者的習慣;
  • 方便多級原型繼承;

通過Class申明的類,內(nèi)部的變量屬性和方法會自動綁定到原型中,可以輕松實現(xiàn)多級繼承,提升開發(fā)效率,下面定義了一個Cat繼承Animal的類:

class Animal {
    constructor(name) {
      this.name = name;
    }
      
    printName() {
      console.log(this.name);
    }
  }  

class Cat extends Animal{
    printName(){
        console.log('Cat:' + this.name);
    }
}

let dog = new Animal('anan');
dog.printName(); // Output ana
console.log(dog instanceof Cat); //Output false -  instanceof 會從原型鏈中找到指定對象的類型,返回true,否則返回false
console.log(dog instanceof Animal); //Output true
let cat = new Cat('fufu');
cat.printName(); //Output fufu
console.log(cat instanceof Cat); //Output true
console.log(cat instanceof Animal);//Output true 

This關鍵字

this在JS中扮演著什么角色?發(fā)揮著什么作用?
this是一個對象的指針,這個地址的值由JS運行時決定的,取決于方法的調(diào)用者,下面的例子可以看到this會指向user,也會指向admin:

let user = { name: "John" };
let admin = { name: "Admin" };

function sayHi() {
  console.log( this.name );
}

// use the same function in two objects
user.f = sayHi;
admin.f = sayHi;

// these calls have different this
// "this" inside the function is the object "before the dot"
user.f(); // John  (this == user)
admin.f(); // Admin  (this == admin)

變量的定義申明決定了變量的可見范圍,其中在變量的可見范圍內(nèi),通過this可以訪問到;this可能指向兩個地址,首先會指向當前調(diào)用者的container object,如果沒有顯式的申明調(diào)用者,則會指向global project。

其中在瀏覽器端,global context指的是window object,this指的是window對象;在Node中,this會指向globalThis(global object),

function increment(incrementBy){
    this.aValue = this.aValue + incrementBy;
}

//Demo 1
var thisPointToObject = {
    aValue: 0
}
thisPointToObject.increment = increment;
thisPointToObject.increment(2);//this point to current obj
console.log(thisPointToObject.aValue); // 2

//Demo 2
global.aValue = 6;
increment(2);//this point to global project
console.log(global.aValue ); // 8
this keyword point to object
this keyword point to global project

Events & Function

前面介紹了JS的數(shù)據(jù)類型包含基本數(shù)據(jù)類型和引用數(shù)據(jù)類型,再結(jié)合程序設計語言的三大基本結(jié)構(順序結(jié)構、選擇分支結(jié)構、循環(huán)結(jié)構),我們就可以編寫復雜的業(yè)務程序,在此之前,有必要了解一下JS中的事件和函數(shù),這兩者是在瀏覽器端是相互依存,瀏覽器端事件的觸發(fā)引起函數(shù)的調(diào)用,才能真正發(fā)揮JS的作用,下面三點是需要了解的:

  1. JS中的事件是由瀏覽器通知的
    網(wǎng)頁上按鈕的點擊、輸入框的變化、光標的移動,都是由DOM組件發(fā)出的事件,這些事件會傳遞給JS引擎,調(diào)用對應的方法;

  2. 函數(shù)(Function)也是一個對象
    正如之前提到的,function是JS中的頭等函數(shù),它們可以像基本數(shù)據(jù)類型一樣進行賦值、作為函數(shù)參數(shù)以及作為函數(shù)返回參數(shù)。

2.1. 方法申明

// Function Declaration
function sayHello(name) {
  return 'Hello:' + name
}
//Function Expression
const sayHello2 = function (name) {
  return 'Hello:' + name
}
//Arrow Function 
let sayhello3 = (name) => 'Hello:' + name
// 方法調(diào)用
let sayHelloInvoker1 = sayHello('Function Declaration');
let sayHelloInvoker2 = sayHello2('Function Expression');
let sayHelloInvoker3 = sayHello2('Arrow Function');
console.log(sayHelloInvoker1);
console.log(sayHelloInvoker2);
console.log(sayHelloInvoker3); 

2.2. 方法調(diào)用

// invoke function
let invokeResult = sayHello('Invoke Function');
console.log(invokeResult);
// assign sayHello function to a new pointer
const sayHelloNew = sayHello;
let invokeResultNew = sayHelloNew('Invoke New Function');
console.log(invokeResultNew);
  1. 函數(shù)(Function)作為事件處理器

3.1. 綁定HTML元素
在HTML中,指定事件調(diào)用的JS方法,如下面例子所示:

<html>
    <head></head>
    <body>
        <button onclick="handleClick(event)">
            Click me
        </button>
    </body>
    <script>
        var handleClick = function(event) {
            console.log(event.type);  // click
        }
    </script>
</html>

3.2. 調(diào)用DOM API
相比較于直接將JS方法綁定到HTML元素中,現(xiàn)在比較流行的方式是直接調(diào)用DOM對象的API,可以提升頁面響應速度,提升用戶體驗,還是上面的例子,我們做出以下改變:

<html>
    <head></head>
    <body>
        <button id="clicker">
            Click me
        </button>
    </body>
    <script>            
        var handleClick = function(event) {
            console.log(event.type);  //output click
        }
        let button = document.getElementById("clicker");
        button.addEventListener("click", handleClick);
        //anonymous functions 
        button.addEventListener("click", function(event){
            console.log(event.type);  //output click
        });
    </script>
</html>

異步特性

當我們訪問網(wǎng)站的時候,如果頁面總是卡頓,總會降低用戶訪問網(wǎng)站的興致,前端頁面會包含很多JS腳本;JS是單線程的一門語言,意味著一個時間點只能處理一件事情,比如現(xiàn)在頁面上一個按鈕綁定了后臺服務器方法,如果用戶點擊按鈕后,需要等待服務器處理結(jié)果才能進行其他操作,這樣的網(wǎng)站用戶體驗十分糟糕,針對這種情況,需要保證頁面不會發(fā)生阻塞,現(xiàn)在有必要引入異步編程,咱們簡單看一下JS是怎么處理的?

通過下面的例子來回顧一下JSRunning引擎的運行:

JSEventLoop

  • 一般情況下,JS引擎會依次順序執(zhí)行代碼片段;
  • 如果出現(xiàn)會導致瀏覽器阻塞的操作,比如請求后臺服務器、獲取數(shù)據(jù)庫數(shù)據(jù)等耗時動作,JS引擎不會馬上執(zhí)行,而是將任務添加到Web API中,待異步操作完成后,將回調(diào)方法放入Callback Queue中;
  • 盡管Callback Queue中有任務,JS引擎也不會立刻執(zhí)行,只有當Stack中任務為空時,Event Loop會執(zhí)行任務隊列中的回調(diào)方法。

下面這段簡短的JS是怎么執(zhí)行的呢?

setTimeout(function(){
  console.log("This comes first");
}, 5000);
console.log("This comes second");

//output in console
// "This comes second"
// "This comes first"
  1. JS引擎識別到setTimeout為異步方法,會將該任務放到WebAPI中,如下圖所示:
Step 1.1
Step 1.2
  1. 在Web APIs執(zhí)行異步任務的過程中,并不會阻礙JS引擎執(zhí)行接下來的代碼,可以看到Stack中加載了console.log("This comes second")的任務,并將其打印在控制臺中;
Step 2.1
Step 2.2
  1. 讓我們繼續(xù)等待異步執(zhí)行完成
Step 3.1
Step 3.2

當異步任務完成后,我們可以看到這個異步操作放入了Callback Queue中,接下來等待Event Loop將該操作放入Stack中,如下圖所示:

Step 3.3
Step 3.4

當Stack、Web APIs、Callback Queue都為空時,這個時候我們可以認為JS引擎完成了上述代碼的執(zhí)行,注意當前我們將timer時間設置為5秒,即使設置為0秒,JS引擎也會將該操作放入到Web APIs中,輸出結(jié)果也是一樣的。

了解JS實現(xiàn)異步的基本原理后,我們可以看到JS中常見的回調(diào)函數(shù)(異步回調(diào))其實是在Web APIs執(zhí)行完異步任務后,Event Loop會將回調(diào)函數(shù)放入到Stack中執(zhí)行,在代碼編寫中,當遇到異步編程時,這個模型更有助于理解JS的執(zhí)行過程和書寫更有效的代碼。

總結(jié)

現(xiàn)如今,JS的發(fā)展速度越來越迅猛,在前后端領域都扮演了重要的角色,在我個人看來,有必要重新審視這門編程語言;感謝您的閱讀,文中如有不足的地方,請大家更正,希望文中內(nèi)容給您帶來思考的同時,可以將其思考內(nèi)容分享給大家。

最后也向您提一個問題:JS是一門弱類型語言,意味著申明變量時不需要指定特定數(shù)據(jù)類型,在解釋執(zhí)行階段,JS解析器仍會在內(nèi)存中分析得出具體數(shù)據(jù)類型;對于一些強類型語言(Java),需要在編寫代碼階段明確對應數(shù)據(jù)類型,那么編程中數(shù)據(jù)類型的明確有什么意義呢?

常用工具:

參考資料:

MDN:
https://developer.mozilla.org/en-US/docs/Web

Javascript Info:
https://javascript.info/

Salesforce Modules:
https://trailhead.salesforce.com/en/content/learn/trails/learn-to-work-with-javascript

ES6 Features:
https://262.ecma-international.org/6.0/

ES6 Compatibility table:
http://kangax.github.io/compat-table/es6/

最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,156評論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,401評論 3 415
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 176,069評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,873評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,635評論 6 408
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,128評論 1 323
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,203評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,365評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,881評論 1 334
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 40,733評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,935評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,475評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,172評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,582評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,821評論 1 282
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,595評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,908評論 2 372

推薦閱讀更多精彩內(nèi)容