Javascript 雜談 :熟悉基本概念

Javascript 雜談 :熟悉基本概念

1. 信息译断、變量萧恕、數(shù)據(jù)類型和變量對象

ECMAScript 對于變量的定義及語法漾狼,借鑒了眾多語言的特性鸠蚪,同時也形成了自己獨特的運作機制今阳。松散的變量和簡單的語法讓編程過程更加愜意自由。

1.1. 看不見的信息

眾所周知茅信,計算機是用來處理信息的工具盾舌,而程序就是處理信息的步驟。但是信息是看不見摸不到的蘸鲸,如何進行操作妖谴?我們首先使用符號來承載它,也就是經(jīng)常提到的數(shù)據(jù),但是這樣好像也不行膝舅,例如在黑漆漆的房間里我畫下了若干符號嗡载,你依然無法感知啊。所以仍稀,要有光洼滚,利用信號讓你感知數(shù)據(jù)和信息。比如這篇文章在我的腦中技潘,你是無法感知的遥巴,我用漢語(符號)將它寫出,然后通過光傳到你的眼睛里崭篡;你的大腦將光信號再翻譯回漢語(符號)挪哄,然后分析它的信息吧秕。

好了琉闪,請描述一下 “20 ” 這個信息。什么我已經(jīng)描述了砸彬,你看到的 “20” 就是這個信息的阿拉伯?dāng)?shù)字符號表示颠毙。那么你是怎么看到這個符號的?真正的過程可能比較復(fù)雜:

  • 我在電腦上輸入 “20” (符號)砂碉,通過 ASCII 轉(zhuǎn)化成了 0蛀蜜、1格式(另一種符號)。
  • 我的網(wǎng)卡將 0增蹭、1 轉(zhuǎn)化成電平(信號)滴某,通過網(wǎng)線傳到你的網(wǎng)卡。
  • 你的網(wǎng)卡重新將電平翻譯回 0滋迈、1格式霎奢,然后根據(jù) ASCII 轉(zhuǎn)化為你屏幕上的 “20” 。

在上述過程中是否可以保存信號來間接的存儲數(shù)據(jù)和信息饼灿?當(dāng)然可以存儲設(shè)備都是這樣工作的幕侠,晶體管的穩(wěn)態(tài)表示 1,光盤的凹凸坑對應(yīng)了 0 碍彭、1 晤硕。我們把 “20” 存在內(nèi)存中,終于把信息從看不見摸不到的狀態(tài)變成了實際存在且能保存的物理信號庇忌。那么程序是如何讀取內(nèi)存中的 “20” 的舞箍?

1.2. 客觀的數(shù)據(jù)

用哲學(xué)的概念來引述,存儲在內(nèi)存中的 “20”皆疹,它是具體存在的疏橄,是客觀的。而將它映射到程序中的就是變量墙基,它是邏輯的(主觀的)软族。

map.png

1.3. 變量只是一個名字

變量在 ECMAScript 中是所謂的標識符刷喜,也就是一個名稱或名字。它的作用只是用來指示某個物體或?qū)ο螅?strong>變量沒有數(shù)據(jù)立砸。比如每個人都有名字掖疮,但是名字里含有你的數(shù)據(jù)嗎?名字只是用來辨識人颗祝,同理變量只是用來分辨具體數(shù)據(jù)或?qū)ο蟮摹?/p>

你可能會問 var a = 20 中的 “20” 不就是一個標識符嗎浊闪?這條語句的具體步驟應(yīng)該是:

  • 人類輸入熟悉的標識符 “20”
  • 編譯器將 “20” 編譯成相應(yīng)類型的數(shù)據(jù)存儲在內(nèi)存中
  • 使用變量 a 來映射內(nèi)存中的數(shù)據(jù)

也就是說這里的 a 和 20 都是內(nèi)存中數(shù)據(jù)的標識符,20 是人類熟悉的標識符螺戳,通常出現(xiàn)在初始化過程搁宾,a 則是程序使用的標識符,并且因為是邏輯的倔幼,所以可以變動盖腿,這也是稱為變量的原因。當(dāng)然在 ECMAScript 中除了 20 這樣的數(shù)字损同,還定義多種數(shù)據(jù)類型翩腐。

1.4. 類型是數(shù)據(jù)的結(jié)構(gòu)

在人類世界中國際標準的基本單位有 7 個:長度m,時間s膏燃,質(zhì)量kg茂卦,熱力學(xué)溫度(Kelvin溫度)K,電流單位A组哩,光強度單位cd(坎德拉)等龙,物質(zhì)的量mol。這些基本單位可以推出物理世界的所有物理量伶贰。而在 ECMAScript 中定義了 6 種數(shù)據(jù)的類型蛛砰,并且不支持自定義類型,因此所有值都是這 6 中數(shù)據(jù)類型之一幕袱。它們又分為兩類暴备,第一類稱為基本數(shù)據(jù)類型,包含:Undefined们豌, Null涯捻, BooleanNumber望迎, String障癌。另外一類稱為復(fù)雜數(shù)據(jù)類型 Object ,它是由多個無序的鍵值對組成辩尊。

根據(jù)數(shù)據(jù)類型的分類涛浙, ECMAScript 中的變量可能映射兩種不同的類型值,基本數(shù)據(jù)類型值和引用類型值〗瘟粒基本數(shù)據(jù)類型值就是該類型的數(shù)據(jù)疮薇,引用類型值則是該類型對象的引用,它們都和變量存儲在內(nèi)存椢易ⅲ空間按咒,而引用類型的對象存儲在內(nèi)存的堆空間。

variable.png

大體上可以認為引用類型就是繼承自 Object 復(fù)雜數(shù)據(jù)類型的一種數(shù)據(jù)結(jié)構(gòu)但骨,它包含了數(shù)據(jù)和與數(shù)據(jù)相關(guān)的操作励七。常用的有 FunctionArray奔缠, Date掠抬, RegExp ,而這些類型數(shù)據(jù)在聲明時就已經(jīng)轉(zhuǎn)化成對象存在內(nèi)存中校哎,只有在沒有引用情況下會被垃圾收集两波。除此之外還有一種特殊的引用類型,就是基本包裝類型贬蛙,在說明基本包裝類型前先了解一下什么是數(shù)據(jù)類型雨女,《數(shù)據(jù)結(jié)構(gòu)》中定義了 “數(shù)據(jù)類型 = 數(shù)據(jù)元素 + 關(guān)系集 + 關(guān)系集的基本操作” ,也就是說數(shù)據(jù)類型不僅僅包含數(shù)據(jù)還有和數(shù)據(jù)相關(guān)的操作阳准。

// 程序中聲明一個 ```string``` 類型數(shù)據(jù)元素,但是注意并沒有定義相關(guān)的操作
var str = 'some text'
var listItem = [1, 2, 3]
// 而在這里使用了```substring``` 方法馏臭,
// 也就是說 ECMAScript 將 'some text' 這個數(shù)據(jù)元素自動轉(zhuǎn)化成某種數(shù)據(jù)類型的數(shù)據(jù)存儲在內(nèi)存中野蝇,隨后釋放。
var sub_str = str.substring(2)   // 'me text'
var subList = listItem.slice(2)  // [3]

在語句 var str = 'some text' 中變量 str 映射的是一個基本數(shù)據(jù)類型的值括儒,注意僅僅是數(shù)值绕沈,但是為什么可以調(diào)用 substring() 方法?這就是基本包裝類型的功能帮寻,基本包裝類型同樣繼承自 Object 乍狐,但是數(shù)據(jù)元素都定義為 Number 這種基本數(shù)據(jù)類型,同時給出一些相關(guān)的基本操作固逗,它包含有 Number浅蚪, StringBoolean 烫罩。是的和基本類型的樣子一模一樣惜傲,這樣的黑箱效應(yīng)讓程序員無須留意轉(zhuǎn)化過程的。程序員看到的是 'some text'贝攒,而程序在調(diào)用它時轉(zhuǎn)化成一種 String 基本包裝類型的對象實例盗誊,但是基本包裝類型特殊處就是只在調(diào)用時轉(zhuǎn)化,調(diào)用結(jié)束后釋放。

// var sub_str = str.substring(2) 的等效程序
var str = new String('some text')
 // 因為此時 str 已經(jīng)是基本包裝類型了哈踱,擁有了 substring 方法
var sub_str = str.substring(2)  
// 最后釋放這個對象數(shù)據(jù)
str = null

可將上述內(nèi)容總結(jié)為:數(shù)據(jù)賦值過程中需要判斷數(shù)據(jù)是基本數(shù)據(jù)類型還是引用類型荒适,基本數(shù)據(jù)類型的數(shù)據(jù)直接賦值給變量,而引用類型的數(shù)據(jù)將引用賦值給變量开镣。

下面通過例子來鞏固學(xué)習(xí)內(nèi)容:

var a = 20
var b = a
b = 30
console.log(a)

第一條語句所賦值的數(shù)據(jù)是 Number 基本數(shù)據(jù)類型吻贿,那么只需要將值映射給變量 a 即可。

第二條語句首先調(diào)用了變量 a 哑子,將數(shù)值 20 復(fù)制給變量 b 映射的數(shù)值中舅列,此時變量 b 所映射的數(shù)值與變量 a 所映射的數(shù)值存在不同的內(nèi)存單元中。這里可能需要補充一個概念卧蜓,在 ECMAScript 中賦值和參數(shù)傳遞都是傳值操作帐要,也就是將一個變量映射的值傳給另一個變量映射的值∶旨椋或者簡單的認為變量和映射的值是一個整體榨惠。

copy.png

第三條語句將基本數(shù)據(jù)類型的數(shù)值 30 映射給變量 b 。

再看一個例子:

var c = [1, 2, 3]
var d = {x: 10, y: 20, z: 30}
var e = c
e[2] = 4
var f = d
f.y = 40

// c: [1, 2, 4], e: [1, 2, 4]
// d: {x: 10, y: 40, z: 30}, f: {x: 10, y: 40, z: 30}

上例中盛霎,c 是 Array 引用類型赠橙, d 是 Object 引用類型,它們的值都是可變的愤炸。而更需要說明的是 e 和 c 同時指向一個對象期揪, d 和 f 也是,當(dāng) e 和 f 被修改時规个,c 和 d 的值也隨之發(fā)生變化凤薛。

referrence.png

1.5. 變量對象

在之前的圖示中,已經(jīng)多次描述內(nèi)存的操作方式诞仓,即棧和堆缤苫。所謂的棧是一種對內(nèi)存單元的操作方式,使得內(nèi)存對外呈現(xiàn)出一種特殊的數(shù)據(jù)結(jié)構(gòu):堆棧墅拭。堆棧本身是一種受限的線性表活玲,其受限主要表現(xiàn)在對數(shù)據(jù)的操作位置只能是棧頂,擁有先進后出(FILO)的特點〉瘢現(xiàn)實中收納乒乓球的盒子就是一個堆棧舒憾。在程序中通常利用堆棧解決嵌套調(diào)用的問題,例如下面的程序:

function sum(a, b) {
  return a + b
}

var result = sum(1, 2)

當(dāng)程序開始執(zhí)行屡萤,編譯器會為此創(chuàng)建一個全局執(zhí)行環(huán)境又稱為執(zhí)行上下文珍剑,以后每進入一個函數(shù),都會創(chuàng)建相應(yīng)的執(zhí)行環(huán)境死陆,并依次將執(zhí)行環(huán)境推入內(nèi)存的椪凶荆空間唧瘾。在執(zhí)行環(huán)境中最重要的兩個部分,一個是變量對象别凤,它用來保存在環(huán)境中所定義的變量和函數(shù)饰序,另一個是作用域鏈(后面章節(jié)說明)。

接下來通過圖示解釋上述程序的執(zhí)行過程规哪,在程序開始執(zhí)行時求豫,全局執(zhí)行環(huán)境被壓入棧空間:

global_variable_object.png

當(dāng)程序調(diào)用 sum 函數(shù)時诉稍,sum 函數(shù)的執(zhí)行環(huán)境被壓入楎鸺危空間:

sum_AO.png

當(dāng)然執(zhí)行完 sum 函數(shù)后,sum 函數(shù)的執(zhí)行環(huán)境就會從棻蓿空間中彈出蚤告,把控制權(quán)交還給之前進入棧空間的執(zhí)行環(huán)境(此例中就是全局執(zhí)行環(huán)境)服爷。

之前已經(jīng)學(xué)習(xí)到變量和其映射的值都保存在內(nèi)存的椂徘。空間中,現(xiàn)在明白了其中的原因仍源,它們作為執(zhí)行環(huán)境中的變量對象壓入進執(zhí)行環(huán)境棧心褐。此時仔細思考會發(fā)現(xiàn)另外一個與變量對象相關(guān)的問題,那就是一個執(zhí)行環(huán)境中的程序是否可以訪問另外一個執(zhí)行環(huán)境的變量對象笼踩?這個問題的答案就是作用域鏈逗爹。

1.6. 作用域鏈

在執(zhí)行某一個環(huán)境中的程序時,會創(chuàng)建一個變量對象的作用域鏈戳表。它的作用是給出該執(zhí)行環(huán)境中的程序能夠有權(quán)訪問的變量和函數(shù)的有序鏈表桶至。作用域鏈的首個結(jié)點始終是當(dāng)前執(zhí)行環(huán)境的變量對象,下一個結(jié)點來自包含的外部環(huán)境匾旭,依此類推直到全局執(zhí)行環(huán)境。全局執(zhí)行環(huán)境的變量對象總是作用域鏈的最后一個結(jié)點圃郊。

請看下面的示例代碼:

function interestRate (x) {
  return function yearBalance (y) {
    return y * (1 + x)
  }
}

var currentDeposit = interestRate(0.03)
var balance = currentDeposit(10000)
console.log(balance)

下圖根據(jù)定義給出了各執(zhí)行環(huán)境的作用域鏈价涝,為了清楚的展現(xiàn)之間的關(guān)系將變量對象從執(zhí)行環(huán)境中分離。

scope_chain.png

程序解析變量是沿著作用域鏈一級一級的搜索持舆,搜索過程始終從作用域鏈的前端開始色瘩,然后逐級向后回溯,直到找到變量為止逸寓。
上例中 yearBalance 函數(shù)通過作用域鏈訪問到了 interestRate 函數(shù)的變量 x 居兆。

最后我們來疏通概念間彼此糾纏的關(guān)系,執(zhí)行環(huán)境是一個函數(shù)在內(nèi)存中的投影竹伸,變量對象是函數(shù)的內(nèi)部數(shù)據(jù)泥栖,而作用域鏈是函數(shù)暴露的接口簇宽。每次執(zhí)行函數(shù),首先將執(zhí)行環(huán)境投影到內(nèi)存吧享,然后根據(jù)作用域鏈獲取參數(shù)魏割,執(zhí)行函數(shù)代碼并修改內(nèi)部數(shù)據(jù)也就是變量對象,這里對于可變的變量對象我們又稱之為活動對象钢颂。期間忽略了許多細節(jié)钞它,如活動對象和作用域鏈的構(gòu)建,垃圾收集等殊鞭。

1.7 變量對象與活動對象的區(qū)別

兩者都指向一個對象這是大家的共識遭垛,在一些文章中將兩者的區(qū)別歸結(jié)于執(zhí)行環(huán)境的不同階段,文中都把執(zhí)行環(huán)境的生命周期看成兩個階段操灿,分別是 創(chuàng)建階段執(zhí)行階段 锯仪,變量對象處于創(chuàng)建階段而活動對象處于執(zhí)行階段。 這是沒有問題的牲尺,但是這些文章中對創(chuàng)建階段的概念還是比較模糊的卵酪,個人認為創(chuàng)建階段所指的是,執(zhí)行環(huán)境從開始到銷毀除了執(zhí)行階段外的所有生命周期谤碳。比如上例中如果進入 yearBalance 執(zhí)行環(huán)境的執(zhí)行階段溃卡,那么 interestRate 執(zhí)行環(huán)境就處在創(chuàng)建階段。

2. 面向?qū)ο?/h1>

2.1. this (strict)

在經(jīng)典的面向?qū)ο笳Z言中蜒简,this 只會出現(xiàn)在類中瘸羡,作為該類實例(對象)的占位符使用。 ECMAScript 部分繼承和實現(xiàn)了上述規(guī)則搓茬,唯一例外之處正是 ECMAScript 中最美妙最古怪的函數(shù)犹赖。之前學(xué)習(xí)到所有的函數(shù)都是 Function 引用類型的實例(對象),按說應(yīng)該沒有 this 這個占位符卷仑,但是有一類函數(shù)起到了類的作用峻村,可以生成對象,這就是構(gòu)造函數(shù)锡凝。因此在函數(shù)中又引入了 this 占位符粘昨,這與經(jīng)典定義格格不入,出現(xiàn)了對象中有另一個對象占位符的混亂概念窜锯,使得 this 在函數(shù)中晦澀難懂张肾。

我們從最基本的定義分析,以此梳理 this 的使用規(guī)則锚扎。

  • 除函數(shù)外吞瞪,this 不會出現(xiàn)在對象中。

    // 對象字面量
    var o = {
      a: 1,
      b: this.a + 1
    }  // Object {a: 1, b: NaN}
    
    // 數(shù)組
    var arr = [1, this, 3] // [1, Object, 3]
    
  • 箭頭函數(shù)是匿名函數(shù)驾孔,不會擔(dān)任構(gòu)造函數(shù)的角色芍秆,因此沒有 this 惯疙。

  • 函數(shù)中的 this 是在函數(shù)被執(zhí)行時才確認。

    回顧之前的內(nèi)容浪听,函數(shù)在運行時創(chuàng)建執(zhí)行環(huán)境螟碎,執(zhí)行環(huán)境中包含變量對象和 this 對象,變量對象是在執(zhí)行代碼過程中形成迹栓,this 對象在執(zhí)行開始獲取并無法修改掉分。

  • 構(gòu)造函數(shù)的 this 來自它所創(chuàng)造的實例。

  • 非構(gòu)造函數(shù)的 this 是引用它的對象克伊。

function Person (name, age) {
  this.name = name
  this.age = age
  this.sayName = sayName
}
function sayName () {
  console.log(this.name)
}

var alien = {
  name: 'alien',
  age: 1000,
  sayName: sayName
}
var tom = new Person('tom', 22)

alien.sayName()
tom.sayName()

上述只是 this 最基本的判斷方法酥郭,如果仔細分析作用域同時結(jié)合閉包思想,可以盡可能的減少 this 的出錯率愿吹。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末不从,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子犁跪,更是在濱河造成了極大的恐慌椿息,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,084評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件坷衍,死亡現(xiàn)場離奇詭異寝优,居然都是意外死亡,警方通過查閱死者的電腦和手機枫耳,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,623評論 3 392
  • 文/潘曉璐 我一進店門段直,熙熙樓的掌柜王于貴愁眉苦臉地迎上來插勤,“玉大人疫诽,你說我怎么就攤上這事唉匾。” “怎么了铅协?”我有些...
    開封第一講書人閱讀 163,450評論 0 353
  • 文/不壞的土叔 我叫張陵捷沸,是天一觀的道長。 經(jīng)常有香客問我狐史,道長亿胸,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,322評論 1 293
  • 正文 為了忘掉前任预皇,我火速辦了婚禮,結(jié)果婚禮上婉刀,老公的妹妹穿的比我還像新娘吟温。我一直安慰自己,他們只是感情好突颊,可當(dāng)我...
    茶點故事閱讀 67,370評論 6 390
  • 文/花漫 我一把揭開白布鲁豪。 她就那樣靜靜地躺著潘悼,像睡著了一般。 火紅的嫁衣襯著肌膚如雪爬橡。 梳的紋絲不亂的頭發(fā)上治唤,一...
    開封第一講書人閱讀 51,274評論 1 300
  • 那天,我揣著相機與錄音糙申,去河邊找鬼宾添。 笑死,一個胖子當(dāng)著我的面吹牛柜裸,可吹牛的內(nèi)容都是我干的缕陕。 我是一名探鬼主播,決...
    沈念sama閱讀 40,126評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼疙挺,長吁一口氣:“原來是場噩夢啊……” “哼扛邑!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起铐然,我...
    開封第一講書人閱讀 38,980評論 0 275
  • 序言:老撾萬榮一對情侶失蹤蔬崩,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后搀暑,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體沥阳,經(jīng)...
    沈念sama閱讀 45,414評論 1 313
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,599評論 3 334
  • 正文 我和宋清朗相戀三年险掀,在試婚紗的時候發(fā)現(xiàn)自己被綠了沪袭。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,773評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡樟氢,死狀恐怖冈绊,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情埠啃,我是刑警寧澤死宣,帶...
    沈念sama閱讀 35,470評論 5 344
  • 正文 年R本政府宣布,位于F島的核電站碴开,受9級特大地震影響毅该,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜潦牛,卻給世界環(huán)境...
    茶點故事閱讀 41,080評論 3 327
  • 文/蒙蒙 一眶掌、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧巴碗,春花似錦朴爬、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,713評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽母赵。三九已至,卻和暖如春具滴,著一層夾襖步出監(jiān)牢的瞬間凹嘲,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,852評論 1 269
  • 我被黑心中介騙來泰國打工构韵, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留周蹭,地道東北人。 一個月前我還...
    沈念sama閱讀 47,865評論 2 370
  • 正文 我出身青樓贞绳,卻偏偏與公主長得像谷醉,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子冈闭,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,689評論 2 354

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