成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

談談javascript語法里一些難點問題(一)

huaixiaoz / 630人閱讀

摘要:引子前不久我建立的技術群里一位問了一個這樣的問題,她貼出的代碼如下所示執(zhí)行結果如下所示第一個第二個這是一個令人詫異的結果,為什么第一個彈出框顯示的是,而不是呢這種疑惑的原理我描述如下一個頁面里直接定義在標簽下的變量是全局變量即屬于對象的變量

1) 引子

前不久我建立的技術群里一位MM問了一個這樣的問題,她貼出的代碼如下所示:

var a = 1;

function hehe()

{

         window.alert(a);

         var a = 2;

         window.alert(a);

}

hehe();

執(zhí)行結果如下所示:

第一個alert:

第二個alert:

這是一個令人詫異的結果,為什么第一個彈出框顯示的是undefined,而不是1呢?這種疑惑的原理我描述如下:

一個頁面里直接定義在script標簽下的變量是全局變量即屬于window對象的變量,按照javascript作用域鏈的原理,當一個變量在當前作用域下找不到該變量的定義,那么javascript引擎就會沿著作用域鏈往上找直到在全局作用域里查找,按上面的代碼所示,雖然函數(shù)內部重新定義了變量的值,但是內部定義之前函數(shù)使用了該變量,那么按照作用域鏈的原理在函數(shù)內部變量定義之前使用該變量,javascript引擎應該會在全局作用域里找到變量定義,而實際情況卻是變量未定義,這到底是怎么回事呢?

當時群里很多人都給出了問題的解答,我也給出了我自己的解答,其實這個問題很久之前我的確研究過,但是剛被問起了我居然還是有個卡殼期,在加上最近研究javascriptMVC的寫法,發(fā)現(xiàn)自己讀代碼時候對new 、prototype、apply以及call的用法任然要體味半天,所以我覺得有必要對javascript基礎語法里比較難理解的問題做個梳理,其實寫博客的一個很大的好處就是寫出來的知識邏輯會比你在腦子里反復梳理的邏輯映像更加的深刻。

   下面開始本文的主要內容,我會從基礎知識一步步講起。
2) Javascript的變量

Java語言里有一句很經(jīng)典的話:在java的世界里,一切皆是對象。

Javascript雖然跟java沒有半點毛關系,但是很多會使用javascript的朋友同樣認為:在javascript的世界里,一切也皆是對象。

其實javascript語言和java語言一樣變量是分為兩種類型:基本數(shù)據(jù)類型和引用類型。

基本類型是指:Undefined、Null、Boolean、Number和String;而引用類型是指多個指構成的對象,所以javascript的對象指的是引用類型。在java里能說一切是對象,是因為java語言里對所有基本類型都做了對象封裝,而這點在javascript語言里也是一樣的,所以提在javascript世界里一切皆為對象也不為過。

但是實際開發(fā)里如果我們對基本類型和引用類型的區(qū)別不是很清晰,就會碰到我們很多不能理解的問題,下面我們來看看下面的代碼:

var str = "sharpxiajun";

str.attr01 = "hello world";

console.log(str);//  運行結果:sharpxiajun

console.log(str.attr01);// 運行結果:undefined

運行之,我們發(fā)現(xiàn)作為基本數(shù)據(jù)類型,我們沒法為這個變量添加屬性,當然方法也同樣不可以,例如下面的代碼:

str.ftn = function(){

    console.log("str ftn");

}

str.ftn();

運行之,結果如下圖所示:

當我們使用引用類型時候,結果就和上面完全不同了,大家請看下面的代碼:

var obj1 = new Object();

obj1.name = "obj1 name";

console.log(obj1.name);// 運行結果:obj1 name

javascript里的基本類型和引用類型的區(qū)別和其他語言類似,這是一個老調長談的問題,但是在現(xiàn)實中很多人都理解它,但是卻很難應用它去理解問題。

Javascript里的基本變量是存放在棧區(qū)的(棧區(qū)指內存里的棧內存),它的存儲結構如下圖所示:

javascript里引用變量的存儲就比基本類型存儲要復雜多,引用類型的存儲需要內存的棧區(qū)和堆區(qū)(堆區(qū)是指內存里的堆內存)共同完成,如下圖所示:

在javascript里變量的存儲包含三個部分:

部分二:棧區(qū)變量的值;部分一:棧區(qū)的變量標示符;

部分二:棧區(qū)變量的值;

部分三:堆區(qū)存儲的對象。

變量不同的定義,這三個部分也會隨之發(fā)生變化,下面我來列舉一些典型的場景:

場景一:如下代碼所示:
var qqq;

console.log(qqq);// 運行結果:undefined

運行結果是undefined,上面的代碼的標準解釋就是變量被命名了,但是還未初始化,此時在變量存儲的內存里只擁有棧區(qū)的變量標示符而沒有棧區(qū)的變量值,當然更沒有堆區(qū)存儲的對象。

場景二:如下代碼所示:
var qqq;

console.log(qqq);// 運行結果:undefined

console.log(xxx);

運行之,結果如下圖所示:

會提示變量未定義。在任何語言里變量未定義就使用都是違法的,我們看到javascript里也是如此,但是我們做javascript開發(fā)時候,經(jīng)常有人會說變量未定義也是可以使用,怎么我的例子里卻不能使用了?那么我們看看下面的代碼:

xxx = "outer xxx";

console.log(xxx);// 運行結果:outer xxx

function testFtn(){

    sss = "inner sss";

    console.log(sss);// 運行結果:outer sss

}

testFtn();

console.log(sss);//運行結果:outer sss

console.log(window.sss);//運行結果:outer sss

在javascript定義變量需要使用var關鍵字,但是javascript可以不使用var預先定義好變量,在javascript我們可以直接賦值給沒有被var定義的變量,不過此時你這么操作變量,不管這個操作是在全局作用域里還是在局部作用域里,變量最終都是屬于window對象,我們看看window對象的結構,如下圖所示:

由這兩個場景我們可以知道在javascript里的變量不能正常使用即報出“xxx is not defined”錯誤(這個錯誤下,后續(xù)的javascript代碼將不能正常運行)只有當這個變量既沒有被var定義同時也沒有進行賦值操作才會發(fā)生,而只有賦值操作的變量不管這個變量在那個作用域里進行的賦值,這個變量最終都是屬于全局變量即window對象。

由上面我列舉的兩個場景我們來理解下引子里網(wǎng)友提出的問題,下面我修改一下代碼,如下所示:

//var a = 1;

function hehe()

{

    console.log(a);

    var a = 2;

    console.log(a);

}

hehe();

結果如下圖所示:

我再改下代碼:

//var a = 1;

function hehe()

{

    console.log(a);

   // var a = 2;

    console.log(a);

}

hehe();

運行之,結果如下所示:

對比二者代碼以及引子里的代碼,我們發(fā)現(xiàn)問題的關鍵是var a=2所引起的。在代碼一里我注釋了全局變量的定義,結果和引子里代碼的結果一致,這說明函數(shù)內部a變量的使用和全局環(huán)境是無關的,代碼二里我注釋了關鍵代碼var a = 2,代碼運行結果發(fā)生了變化,程序報錯了,的確很讓人困惑,困惑之處在于局部作用域里變量定義的位置在變量第一次使用之后,但是程序沒有報錯,這不符合javascript變量未定義既要報錯的原理。

其實這個變量任然被定義即內存存儲里有了標示符,只不過沒有被賦值,代碼一則說明,內部變量a已經(jīng)和外部環(huán)境無關,怎么回事?如果我們按照代碼運行是按照順序執(zhí)行的邏輯來理解,這個代碼也就沒法理解。

其實javascript里的變量和其他語言有很大的不同,javascript的變量是一個松散的類型,松散類型變量的特點是變量定義時候不需要指定變量的類型,變量在運行時候可以隨便改變數(shù)據(jù)的類型,但是這種特性并不代表javascript變量沒有類型,當變量類型被確定后javascript的變量也是有類型的。但是在現(xiàn)實中,很多程序員把javascript松散類型理解為了javascript變量是可以隨意定義即你可以不用var定義,也可以使用var定義,其實在javascript語言里變量定義沒有使用var,變量必須有賦值操作,只有賦值操作的變量是賦予給window,這其實是javascript語言設計者提升javascript安全性的一個做法。

此外javascript語言的松散類型的特點以及運行時候隨時更改變量類型的特點,很多程序員會認為javascript變量的定義是在運行期進行的,更有甚者有些人認為javascript代碼只有運行期,其實這種理解是錯誤的,javascript代碼在運行前還有一個過程就是:預加載,預加載的目的是要事先構造運行環(huán)境例如全局環(huán)境,函數(shù)運行環(huán)境,還要構造作用域鏈(關于作用域鏈和環(huán)境,本文后續(xù)會做詳細的講解),而環(huán)境和作用域的構造的核心內容就是指定好變量屬于哪個范疇,因此在javascript語言里變量的定義是在預加載完成而非在運行時期。

所以,引子里的代碼在函數(shù)的局部作用域下變量a被重新定義了,在預加載時候a的作用域范圍也就被框定了,a變量不再屬于全局變量,而是屬于函數(shù)作用域,只不過賦值操作是在運行期執(zhí)行(這就是為什么javascript語言在運行時候會改變變量的類型,因為賦值操作是在運行期進行的),所以第一次使用a變量時候,a變量在局部作用域里沒有被賦值,只有棧區(qū)的標示名稱,因此結果就是undefined了。

不過賦值操作也不是完全不對預加載產(chǎn)生影響,預加載時候javascript引擎會掃描所有代碼,但不會運行它,當預加載掃描到了賦值操作,但是賦值操作的變量有沒有被var定義,那么該變量就會被賦予全局變量即window對象。

根據(jù)上面的內容我們還可以理解下javascript兩個特別的類型:undefined和null,從javascript變量存儲的三部分角度思考,當變量的值為undefined時候,那么該變量只有棧區(qū)的標示符,如果我們對undefined的變量進行賦值操作,如果值是基本類型,那么棧區(qū)的值就有值了,如果棧區(qū)是對象那么堆區(qū)會有一個對象,而棧區(qū)的值則是堆區(qū)對象的地址,如果變量值是null的話,我們很自然認為這個變量是對象,而且是個空對象,按照我前面講到的變量存儲的三部分考慮:當變量為null時候,棧區(qū)的標示符和值都會有值,堆區(qū)應該也有,只不過堆區(qū)是個空對象,這么說來null其實比undefined更耗內存了,那么我們看看下面的代碼:

var ooo = null;

console.log(ooo);// 運行結果:null

console.log(ooo == undefined);// 運行結果:true

console.log(ooo == null);// 運行結果:true

console.log(ooo === undefined);// 運行結果:false

console.log(ooo === null);// 運行結果:true

運行之,結果很震驚啊,null居然可以和undefined相等,但是使用更加精確的三等號“===”,發(fā)現(xiàn)二者還是有點不同,其實javascript里undefined類型源自于null即null是undefined的父類,本質上null和undefined除了名字這個馬甲不同,其他都是一樣的,不過要讓一個變量是null時候必須使用等號“=”進行賦值了。

當變量為undefined和null時候我們如果濫用它javascript語言可能就會報錯,后續(xù)代碼會無法正常運行,所以javascript開發(fā)規(guī)范里要求變量定義時候最好馬上賦值,賦值好處就是我們后面不管怎么使用該變量,程序都很難因為變量未定義而報錯從而終止程序的運行,例如上文里就算變量是string基本類型,在變量定義屬性程序還是不會報錯,這是提升程序健壯性的一個重要手段,由引子的例子我們還知道,變量定義最好放在變量所述作用域的最前端,這么做也是保證代碼健壯性的一個重要手段。

下面我們再看一段代碼:

var str;

if (undefined != str && null != str && "" != str){

    console.log("true");

}else{

    console.log("false");

}

if (undefined != str && "" != str){

    console.log("true");

}else{

    console.log("false");

}

if (null != str && "" != str){

    console.log("true");

}else{

    console.log("false");

}

if (!!str){

    console.log("true");

}else{

    console.log("false");

}

str = "";

if (!!str){

    console.log("true");

}else{

    console.log("false");

}

運行之,結果都是打印出false。

使用雙等號“==”,undefined和null是一回事,所以第一個if語句的寫法完全多余,增加了不少代碼量,而第二種和第三種寫法是等價,究其本質前三種寫法本質都是一致的,但是現(xiàn)實中很多程序員會選用寫法一,原因就是他們還沒理解undefined和null的不同,第四種寫法是更加完美的寫法,在javascript里如果if語句的條件是undefined和null,那么if判斷的結果就是false,使用!運算符if計算結果就是true了,再加一個就是false,所以這里我建議在書寫javascript代碼時候判斷代碼是否為未定義和null時候最好使用!運算符。

代碼四里我們看到當字符串被賦值了,但是賦值是個空字符串時候,if的條件判斷也是false,javascript里有五種基本類型,undefined、null、boolean、Number和string,現(xiàn)在我們發(fā)現(xiàn)除了Number都可以使用!來判斷if的ture和false,那么基本類型Number呢?

var num = 0;

if (!!num){

    console.log("true");

}else{

    console.log("false");

}

運行之,結果是false。

如果我們把num改為負數(shù)或正數(shù),那么運行之的結果就是true了。

這說明了一個道理:我們定義變量初始化值的時候,如果基本類型是string,我們賦值空字符串,如果基本類型是number我們賦值為0,這樣使用if語句我們就可以判斷該變量是否是被使用過了。

但是當變量是對象時候,結果卻不一樣了,如下代碼:

var obj = {};

if (!!obj){

    console.log("true");

}else{

    console.log("false");

}

運行之,代碼是true。

所以在定義對象變量時候,初始化時候我們要給變量賦予null,這樣if語句就可以判斷變量是否初始化過。

其實if加上!運算判斷對象的現(xiàn)象還有玄機,這個玄機要等我把場景三講完才能說清楚哦。

場景三:復制變量的值和函數(shù)傳遞參數(shù)

首先看看這個場景的代碼:

var s1 = "sharpxiajun";

var s2 = s1;

console.log(s1);//// 運行結果:sharpxiajun

console.log(s2);//// 運行結果:sharpxiajun

s2 = "xtq";

console.log(s1);//// 運行結果:sharpxiajun

console.log(s2);//// 運行結果:xtq

上面是基本類型變量的賦值,我們再看看下面的代碼:

var obj1 = new Object();

obj1.name = "obj1 name";

console.log(obj1.name);// 運行結果:obj1 name

var obj2 = obj1;

console.log(obj2.name);// 運行結果:obj1 name

obj1.name = "sharpxiajun";

console.log(obj2.name);// 運行結果:sharpxiajun

我們發(fā)現(xiàn)當復制的是對象,那么obj1和obj2兩個對象被串聯(lián)起來了,obj1變量里的屬性被改變時候,obj2的屬性也被修改。

函數(shù)傳遞參數(shù)的本質就是外部的變量復制到函數(shù)參數(shù)的變量里,我們看看下面的代碼:

function testFtn(sNm,pObj){

    console.log(sNm);// 運行結果:new Name

    console.log(pObj.oName);// 運行結果:new obj

    sNm = "change name";

    pObj.oName = "change obj";

}

var sNm = "new Name";

var pObj = {oName:"new obj"};

testFtn(sNm,pObj);

console.log(sNm);// 運行結果:new Name

console.log(pObj.oName);// 運行結果:change obj

這個結果和變量賦值的結果是一致的。

在javascript里傳遞參數(shù)是按值傳遞的。

上面函數(shù)傳參的問題是很多公司都愛面試的問題,其實很多人都不知道javascript傳參的本質是怎樣的,如果把上面?zhèn)鲄⒌睦痈牡膹碗s點,很多朋友都會栽倒到這個面試題下。

為了說明這個問題的原理,就得把上面講到的變量存儲原理綜合運用了,這里我把前文的內容再復述一遍,兩張圖,如下所示:

這是基本類型存儲的內存結構。

這是引用類型存儲的內存結構。

還有個知識,如下:

在javascript里變量的存儲包含三個部分:

部分一:棧區(qū)的變量標示符;

部分二:棧區(qū)變量的值;

部分三:堆區(qū)存儲的對象。

在javascript里變量的復制(函數(shù)傳參也是變量賦值)本質是傳值,這個值就是棧區(qū)的值,而基本類型的內容是存放在棧區(qū)的值里,所以復制基本變量后,兩個變量是獨立的互不影響,但是當復制的是引用類型時候,復制操作還是復制棧區(qū)的值,但是這個時候值是堆區(qū)對象的地址,因為javascript語言是不允許操作堆內存,因此堆內存的變量并沒有被復制,所以復制引用對象復制的值就是堆內存的地址,而復制雙方的兩個變量使用的對象是相同的,因此復制的變量其中一個修改了對象,另一個變量也會受到影響。

原理講完了,下面我列舉一個拔高的例子,代碼如下:

var ftn1 = function(){

    console.log("test:ftn1");

};

var ftn2 = function(){

    console.log("test:ftn2");

};

function ftn(f){

   f();

   f = ftn2;

}

ftn(ftn1);// 運行結果:test:ftn1

console.log("====================華麗的分割線======================");

ftn1();// 運行結果:test:ftn1

這個代碼是很早之前有位朋友考我的,我當時答對了,但是我是蒙的,問我的朋友答錯了,其實當時我們兩個都沒搞懂其中緣由,我朋友是這么分析的他認為f是函數(shù)的參數(shù),屬于函數(shù)的局部作用域,因此更改f的值,是沒法改變ftn1的值,因為到了外部作用域f就失效了,但是這種解釋很難說明我上文里給出的函數(shù)傳參的實例,其實這個問題答案就是函數(shù)傳參的原理,只不過這里加入了個混淆因素函數(shù),在javascript函數(shù)也是對象,局部作用域里f = ftn2操作是將f在棧區(qū)的地址改為了ftn2的地址,對外部的ftn1和ftn2沒有任何改變。

記?。簀avascript里變量復制和函數(shù)傳參都是在傳遞棧區(qū)的值。

棧區(qū)的值除了變量復制起作用,它在if語句里也會起到作用,當棧區(qū)的值為undefined、null、“”(空字符串)、0、false時候,if的條件判斷則是為false,我們可以通過!運算符計算,因此當我們的代碼如下:

var obj = {};

if (!!obj){

    console.log("true");

}else{

    console.log("false");

}

結果則是true,因為var obj = {}相當于var obj = new Object(),雖然對象里沒什么內容,但是在堆區(qū)里,對象的內存已經(jīng)分配了,而變量棧區(qū)的值已經(jīng)是內存地址了,所以if語句判斷就是true了。

看來本主題又沒法寫完,其實本來我寫本文是想講new,prototype,call(apply)以及this,沒想講變量定義就講了這么多,算了,先發(fā)表出來吧,吃了晚飯接著寫,希望今天寫完。

原文出處:談談javascript語法里一些難點問題(一)

文章版權歸作者所有,未經(jīng)允許請勿轉載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉載請注明本文地址:http://systransis.cn/yun/85558.html

相關文章

  • 談談javascript語法難點問題(二)

    摘要:講作用域鏈首先要從作用域講起,下面是百度百科里對作用域的定義作用域在許多程序設計語言中非常重要。原文出處談談語法里一些難點問題二 3) 作用域鏈相關的問題 作用域鏈是javascript語言里非常紅的概念,很多學習和使用javascript語言的程序員都知道作用域鏈是理解javascript里很重要的一些概念的關鍵,這些概念包括this指針,閉包等等,它非常紅的另一個重要原因就...

    Enlightenment 評論0 收藏0
  • 你需要知道面試中的10個JavaScript概念

    摘要:自我學習目前有成千上萬的年輕人在學習和開發(fā),希望獲得一份工作。知道的綁定規(guī)則。知道和原型屬性是什么以及它們的作用。高階函數(shù)了解函數(shù)是中的一級對象,這意味著什么知道從另一個函數(shù)返回函數(shù)是完全合法的。了解閉包和高階函數(shù)允許我們使用的情況。 翻譯原文出處:10 JavaScript concepts you need to know for interviews 之前不是鬧得沸沸揚揚的大漠窮...

    YacaToy 評論0 收藏0
  • 前端_JavaScript

    摘要:為此決定自研一個富文本編輯器。例如當要轉化的對象有環(huán)存在時子節(jié)點屬性賦值了父節(jié)點的引用,為了關于函數(shù)式編程的思考作者李英杰,美團金融前端團隊成員。只有正確使用作用域,才能使用優(yōu)秀的設計模式,幫助你規(guī)避副作用。 JavaScript 專題之惰性函數(shù) JavaScript 專題系列第十五篇,講解惰性函數(shù) 需求 我們現(xiàn)在需要寫一個 foo 函數(shù),這個函數(shù)返回首次調用時的 Date 對象,注意...

    Benedict Evans 評論0 收藏0
  • JS筆記

    摘要:從最開始的到封裝后的都在試圖解決異步編程過程中的問題。為了讓編程更美好,我們就需要引入來降低異步編程的復雜性。異步編程入門的全稱是前端經(jīng)典面試題從輸入到頁面加載發(fā)生了什么這是一篇開發(fā)的科普類文章,涉及到優(yōu)化等多個方面。 TypeScript 入門教程 從 JavaScript 程序員的角度總結思考,循序漸進的理解 TypeScript。 網(wǎng)絡基礎知識之 HTTP 協(xié)議 詳細介紹 HTT...

    rottengeek 評論0 收藏0

發(fā)表評論

0條評論

最新活動
閱讀需要支付1元查看
<