整合營(yíng)銷服務(wù)商

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

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

          一文詳解JavaScript的變量,超詳細(xì),建議收藏!

          文首發(fā)自「慕課網(wǎng)」,想了解更多IT干貨內(nèi)容,程序員圈內(nèi)熱聞,歡迎關(guān)注!

          作者|慕課網(wǎng)精英講師 然冬

          變量就是存放一些內(nèi)容容器

          對(duì)于初學(xué)者,理解變量是重要的一環(huán)。

          從分析變量這個(gè)名詞,可以知道他是一個(gè)可以改變的量,這里的量就是代表某一種

          在 JavaScript 中,變量就是一個(gè)用來(lái)存放值的容器,并且可以對(duì)容器中的值做修改。

          每個(gè)變量都有唯一的變量名,使用變量名來(lái)區(qū)分變量。

          1. 聲明變量

          在 JavaScript 中使用var關(guān)鍵字來(lái)聲明變量。

          var 存放數(shù)字用的變量 = 996;
          
          console.log(存放數(shù)字用的變量); // 輸出:996
          代碼塊123

          上述這段代碼就是申明了一個(gè)名為存放數(shù)字用的變量的變量,并且將它的值設(shè)為996

          使用 console.log,括號(hào)內(nèi)放置變量名,即可將變量的值輸出在控制臺(tái)。

          其中 // 后面的內(nèi)容為注釋,代碼執(zhí)行過(guò)程中會(huì)被忽略。

          雖然使用中文作為變量名在 chrome 瀏覽器下沒有報(bào)錯(cuò),但是還是不建議使用。

          常規(guī)場(chǎng)景中不會(huì)有使用中文名作為變量的情況

          所以上述例子中的變量名不可取。

          var number = 996;
          
          console.log(number); // 輸出:996
          代碼塊123

          存放數(shù)字用的變量修改成 number ,執(zhí)行結(jié)果是一樣的。

          2. 賦值

          給變量設(shè)置值的操作稱為賦值操作。

          2.1 最簡(jiǎn)單的賦值操作

          var result = 0;
          
          console.log(result); // 輸出:0
          代碼塊123

          這是一個(gè)最簡(jiǎn)單的賦值操作,直接將值賦給變量。

          通常只有一個(gè)等號(hào)出現(xiàn)的情況下就存在賦值操作。

          2.2 將計(jì)算結(jié)果賦值給變量

          var result = 2 + 3;
          
          console.log(result); // 輸出:5
          代碼塊123

          這也是一個(gè)賦值操作,只不過(guò)等號(hào)右邊的 2 + 3 會(huì)被計(jì)算出結(jié)果(計(jì)算的方式和小學(xué)開始學(xué)習(xí)的自然數(shù)學(xué)一樣),再賦給變量 result

          將上面這個(gè)例子做一個(gè)簡(jiǎn)單的改寫:

          2.3 讓變量也參與計(jì)算

          var number1 = 2;
          var number2 = 3;
          
          var result = number1 + number2; // 2 + 3
          
          console.log(result); // 輸出:5
          代碼塊123456

          原本 2 + 3 這部分也可以被變量所代替,參與計(jì)算的就是變量中的值。

          2.4 改變變量的值

          var string = '今天加班?';
          
          console.log(string); // 輸出:今天加班?
          
          string = '福報(bào)!';
          
          console.log(string); // 輸出:福報(bào)!
          代碼塊1234567

          注意:

          這里賦給變量的值和之前有點(diǎn)不一樣,是中文文字。

          當(dāng)需要用變量存放一些“字”的時(shí)候,就需要用單引號(hào)'或者雙引號(hào)"將需要存放的字包裹。

          通常單個(gè)字會(huì)稱之為字符,多個(gè)字的時(shí)候稱為字符串

          這里做個(gè)了解,具體的會(huì)在后續(xù)數(shù)據(jù)類型章節(jié)詳細(xì)展開討論。

          這段代碼運(yùn)行后可以在控制臺(tái)觀察到有兩個(gè)輸出,分別對(duì)應(yīng)變量的值。

          代碼很簡(jiǎn)單,先聲明了一個(gè)叫 string 的變量,并賦值字符串今天加班?并輸出,隨后修改了他的值,重新賦值了字符串福報(bào)!

          這是變量最重要的一個(gè)特性:可變

          3. 變量的命名規(guī)范

          在 JavaScript 中變量名存在一定規(guī)范,所有變量名必須符合這些規(guī)范,否則程序無(wú)法執(zhí)行。

          3.1 變量名必須使用字母、下劃線(_)、美元符號(hào)($)開頭

          盡管之前的例子有用到中文作為變量名,但是是不推薦的。

          // 不會(huì)報(bào)錯(cuò)但是不推薦
          var 數(shù)字 = 1;
          // 錯(cuò)誤
          var 1number = 1;
          // 錯(cuò)誤
          var number@a = 1;
          // 錯(cuò)誤
          var num+aa = 2;
          
          //下面是正確的方式
          var number1 = 1;
          var _number = 1;
          var $number = 1;
          代碼塊12345678910111213

          以上是一些簡(jiǎn)單的示例,可以根據(jù)規(guī)則自己在控制臺(tái)嘗試尋找規(guī)則。

          3.2 變量對(duì)大小寫敏感

          // 這是兩個(gè)不同的變量
          var firstName = 'Hello';
          var firstname = 'hello';
          代碼塊123

          以上是兩個(gè)不同的變量,在 JavaScript 中變量是對(duì)大小寫敏感的。

          兩個(gè)變量名即便字母是相同的,但是大小寫不同,就不能算做一個(gè)變量。

          3.3 無(wú)法使用關(guān)鍵字作為變量名

          關(guān)鍵字就是指一些已經(jīng)被 JavaScript 預(yù)定義或者保留下來(lái)的內(nèi)容,如聲明變量用的關(guān)鍵字 var 就不能作為變量名。

          var var = 1; // Uncaught SyntaxError: Unexpected token 'var'
          代碼塊1

          上面這段代碼嘗試著將 var 作為變量,到控制臺(tái)運(yùn)行是會(huì)報(bào)錯(cuò)的。

          4. 合理規(guī)范的變量名

          剛開始學(xué)習(xí)的讀者,現(xiàn)在去深究如何命名一個(gè)變量還有些尚早,因?yàn)榻Y(jié)合了具體的需求場(chǎng)景才能體會(huì)到一個(gè)好的變量名的重要性。可以先在此做個(gè)了解。

          對(duì)于變量名,除了上面提到的變量命名的規(guī)范,最需要注意的就是給變量起一個(gè)有意義的名字。

          如求和:

          var num1 = 1;
          var num2 = 2;
          var num3 = 3;
          var num4 = 4;
          
          var count = num1 + num2 + num3 + num4;
          代碼塊123456

          其中numnumber的縮寫,是很常用的一種縮寫。

          count則是總數(shù),表示求和的結(jié)果。

          如果將上述例子做如下修改:

          var a = 1;
          var b = 2;
          var c = 3;
          var d = 4;
          
          var e = a + b + c + d;
          代碼塊123456

          缺少了有意義的變量名就比較難看出代碼具體在做什么。當(dāng)然這段代碼本身意義就不大,場(chǎng)景太過(guò)簡(jiǎn)單。

          剛才提到的縮寫,其實(shí)也是要注意的一點(diǎn),縮寫上一定要使用通用的縮寫,如含有fn表示一個(gè)功能或者函數(shù),avg 表示平均值,pwd 表示密碼,i18n 為國(guó)際化。

          這些縮寫比較通用,大部分開發(fā)者都可以看得懂。隨著編碼經(jīng)驗(yàn)的增加,會(huì)在他人代碼里見到大量的縮寫,從而累積到自己的大腦的縮寫庫(kù)中。

          最后需要注意的一點(diǎn)是業(yè)務(wù)中盡量不要含有中文拼音或中文拼音的縮寫,排開鄙視鏈的原因,最大的問(wèn)題是會(huì)讓變量名變得冗長(zhǎng)難懂。

          以上內(nèi)容在寫 demo 或者測(cè)試功能的時(shí)候可以不需要考慮,寫 demo 等大部分情況是為了驗(yàn)證自己的猜想。

          // 不合理的變量名
          var ln = 'World'; // last name
          var zs = 0; // 總數(shù)
          var jinNianDeNianShouRu = 1999999999; // 今年的年收入
          代碼塊1234

          以上是針對(duì)變量名的意義展開的討論。

          還有需要注意的是變量命名的格式,大部分前端程序員會(huì)使用駝峰命名法,也就是第一個(gè)字母小寫,后續(xù)如果有新的單詞來(lái)進(jìn)行構(gòu)成,單詞的第一個(gè)字符都大寫。

          如:

          var firstName = 'Hello';
          
          var lastName = 'world';
          
          var createAt = 1577895179196;
          
          var userInfo = '用戶信息'; // Info => Information
          
          var isPaidUser = '是否付費(fèi)用戶';
          代碼塊123456789

          可以見到上面的變量,從構(gòu)成變量名的第二個(gè)單詞開始,首字母都是大寫,這就是駝峰命名的格式,本 Wiki 所有變量名使用的就是這種格式。

          當(dāng)然還有大駝峰,就是第一個(gè)字母也大寫。

          除此之外最常用的還有使用下劃線分隔變量,如 user_info,還有按功能來(lái)劃分的變量名,如使用匈牙利命名法,這里不再做展開。

          5. 有關(guān)變量的其他知識(shí)

          5.1 變量的默認(rèn)值

          變量在聲明的時(shí)候,如果沒有賦值,則變量就會(huì)有一個(gè)默認(rèn)值 undefined

          var total;
          
          console.log(total); // 輸出:undefined
          代碼塊123

          undefined 是一種是數(shù)據(jù)類型,具體內(nèi)容可以參考數(shù)據(jù)類型章節(jié)。

          5.2 同時(shí)聲明多個(gè)變量

          使用一個(gè) var 關(guān)鍵字就可以直接聲明多個(gè)變量。

          var num1 = 0, num2 = 1;
          
          // 通常會(huì)換行,方便閱讀代碼
          var num3 = 2,
              num4 = 3,
              num5 = 4,
              num6,
              num7 = 6;
          代碼塊12345678

          在一個(gè)變量聲明后,使用逗號(hào)分隔,緊接著聲明下一個(gè)變量即可。

          通常使用一個(gè) var 聲明多個(gè)變量的時(shí)候也會(huì)換行,方便后續(xù)閱讀,并保持代碼格式上的整潔清晰,防止一行過(guò)長(zhǎng)。

          5.3 變量在 window 上

          在最外層聲明的變量(不包括 ES6 模塊的情況),實(shí)際上是變成了 window 對(duì)象的一個(gè)屬性。

          var number = 996;
          
          console.log(number); // 輸出:996
          console.log(window.number); // 輸出:996
          代碼塊1234

          上述代碼執(zhí)行后輸出的兩個(gè)內(nèi)容是一樣的,都為 996。有關(guān) window 對(duì)象的內(nèi)容可以參考 BOM 章節(jié)。

          細(xì)心的讀者應(yīng)該會(huì)注意到最外層這個(gè)條件,因?yàn)樽兞窟€有可能聲明在函數(shù)之中,函數(shù)有自己獨(dú)立的作用域,通常在函數(shù)中使用 var 關(guān)鍵字聲明的變量,只在函數(shù)中有效。

          至于為什么可以省略 window 直接訪問(wèn)到變量,可以參考作用域鏈章節(jié)。

          5.4 不使用 var 關(guān)鍵字聲明的變量

          假如不使用 var 關(guān)鍵字,直接創(chuàng)建變量并賦值:

          total = 10;
          
          console.log(total); // 輸出:10
          代碼塊123

          在控制臺(tái)運(yùn)行后會(huì)發(fā)現(xiàn)其實(shí)并沒有報(bào)錯(cuò),輸出的結(jié)果也正常。

          在非ES6模塊中,這樣創(chuàng)建的變量和使用 var 創(chuàng)建的變量除了不能提前使用之外,沒有其他大的區(qū)別,會(huì)被直接作為 window 對(duì)象的屬性,成為全局變量。

          即便是在函數(shù)或者其他存在塊級(jí)作用域的環(huán)境中,這樣聲明的變量也會(huì)作為全局變量。

          5.5 連續(xù)賦值

          var a = b = 1;
          代碼塊1

          假如把上面這行代碼拆開來(lái)可以理解成是這樣的:

          b = 1;
          var a = b;
          代碼塊12

          看似沒什么問(wèn)題,許多開發(fā)者也會(huì)用這種方式同時(shí)聲明多個(gè)變量,但如果在函數(shù)或者獨(dú)立的作用域中,b 就會(huì)成為全局變量,造成全局命名空間的污染。

          5.6 重復(fù)聲明變量

          按照之前說(shuō)的,變量在聲明的時(shí)候如果沒有賦值,則會(huì)是 undefined,這個(gè)規(guī)則在重復(fù)聲明的情況下不適用。

          var num = 1;
          var num;
          
          console.log(num); // 輸出:1
          代碼塊1234

          觀察上面這個(gè)例子輸出的結(jié)果,可以發(fā)現(xiàn)變量 num 的值并沒有改變。

          但是如果重新聲明的同時(shí)做賦值操作,值就會(huì)改變。

          var num = 1;
          var num = 3;
          
          console.log(num); // 輸出:3
          代碼塊1234

          這個(gè)例子輸出的結(jié)果,就是再次聲明并賦值后的值。

          5.7 提前使用變量

          console.log(number); // 輸出:undefined
          
          var number = 1;
          代碼塊123

          這個(gè)例子先輸出了 number 的值,再聲明并對(duì)其進(jìn)行賦值。

          代碼并沒有報(bào)錯(cuò),但如果沒有第二行聲明,只輸出 number

          console.log(number); // Uncaught ReferenceError: number is not defined
          代碼塊1

          這樣子會(huì)爆出變量未定義的錯(cuò)誤,說(shuō)明變量是可以被提前使用,只是沒有值,或者說(shuō)是 undefined 默認(rèn)值。

          具體原因可以參考執(zhí)行上下文章節(jié)。

          這里簡(jiǎn)單的解釋可以理解成,在瀏覽器執(zhí)行的時(shí)候,會(huì)把代碼調(diào)整成如下樣子:

          var number;
          
          console.log(number); // 這個(gè)時(shí)候 number 還沒有被賦值,所以輸出 undefined
          
          number = 1;
          代碼塊12345

          5.8 常量

          常量就是定義并賦值后再也不能修改的量,通常一些不會(huì)改變的量,如配置、物理值等會(huì)聲明為常量,在 ES6 之前是沒有提供常量這一特性的。

          但是根據(jù)常量自身的特性,定義賦值后不能被修改,就可以通過(guò)一些方式來(lái)模擬常量。

          第一種就是采用約定的形式,通常常量都是大寫,不同單詞之間用下劃線分隔。

          var PI = 3.1415926535;
          
          var DB_ACCOUNT = 'root';
          var DB_PASSWORD = 'root';
          代碼塊1234

          這種方式定義的常量本質(zhì)上還是變量,值還是可以修改的,但因?yàn)槊袷讲捎?span style="color: #4C76C9; --tt-darkmode-color: #4C76C9;">國(guó)際慣例,一眼就能看出是常量,不會(huì)對(duì)其修改。

          這種方式是最簡(jiǎn)單的方式,但不安全。

          第二種方式就是利用對(duì)象下屬性的描述來(lái)控制可寫性,將對(duì)象的屬性設(shè)置為只讀。

          var CONFIG = {};
          
          Object.defineProperty(CONFIG, 'DB_ACCOUNT', {
            value: 'root',
            writable: false,
          });
          
          console.log(CONFIG.DB_ACCOUNT); // 輸出:root
          
          CONFIG.DB_ACCOUNT = 'guest';
          
          console.log(CONFIG.DB_ACCOUNT); // 因?yàn)椴豢杀桓膶懀暂敵觯簉oot
          代碼塊123456789101112

          這種方式將常量都放在一個(gè)對(duì)象下,通過(guò)Object.defineProperty定義屬性,設(shè)定其writablefalse,就可以防止被改寫。

          但有一個(gè)問(wèn)題,CONFIG自身這個(gè)對(duì)象可能被修改。

          換一個(gè)思路,既然在最外層聲明的變量是放在window上的,那可以用這個(gè)方式往 window上掛不可改寫的屬性。

          Object.defineProperty(window, 'DB_ACCOUNT', {
            value: 'root',
            writable: false,
          });
          
          console.log(DB_ACCOUNT); // 輸出:root
          
          DB_ACCOUNT = 'guest';
          
          console.log(DB_ACCOUNT); // 因?yàn)椴豢杀桓膶懀暂敵觯簉oot
          代碼塊12345678910

          通常情況下 window 對(duì)象是不可被修改的,這樣常量的安全系數(shù)就變得非常高,但缺點(diǎn)是可能性較差,通過(guò)一點(diǎn)修改可以提升可讀性。

          var define = function(name, value) {
            Object.defineProperty(window, name, {
              value: value,
              writable: false,
            });
          };
          
          define('DB_ACCOUNT', 'root');
          define('DB_PASSWORD', 'root');
          代碼塊123456789

          只要約定好使用 define 函數(shù)定義的都為常量即可。

          還有兩種方式,就是結(jié)合Object.sealObject.freeze的特性來(lái)聲明常量。

          前者可以使對(duì)象不能再被擴(kuò)充,但是所有屬性還需要再手動(dòng)設(shè)置不可寫,后者可以讓對(duì)象不能擴(kuò)充,屬性也不能修改。

          這里對(duì)這兩個(gè)原生方法不再做過(guò)多描述,有興趣可以查閱相關(guān) API 資料。

          6. 小結(jié)

          變量就是存放值的容器。

          變量名存在一些命名規(guī)則:

          • 變量名必須使用字母下劃線(_)美元符號(hào)($)開頭;
          • 變量對(duì)大小寫敏感;
          • 無(wú)法使用關(guān)鍵字作為變量名。

          同時(shí)起變量名的時(shí)候需要有意義,靠近上下文場(chǎng)景。

          歡迎關(guān)注「慕課網(wǎng)」,發(fā)現(xiàn)更多IT圈優(yōu)質(zhì)內(nèi)容,分享干貨知識(shí),幫助你成為更好的程序員!

          接: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ú)一無(wú)二的值),而引用數(shù)據(jù)類型統(tǒng)稱為Object對(duì)象,主要包括對(duì)象、數(shù)組和函數(shù)。在參數(shù)傳遞方式上,基本類型是按值傳遞,引用類型是按共享傳遞

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

          基本類型和引用類型存儲(chǔ)于內(nèi)存的位置不同,基本類型直接存儲(chǔ)在棧中,而引用類型的對(duì)象存儲(chǔ)在堆中,與此同時(shí),在棧中存儲(chǔ)了指針,而這個(gè)指針指向正是堆中實(shí)體的起始位置。下面通過(guò)一個(gè)小題目,來(lái)看下兩者的主要區(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 是用來(lái)判斷A是否為B的實(shí)例,表達(dá)式為:A instanceof B,如果A是B的實(shí)例,則返回true,否則返回false。instanceof 運(yùn)算符用來(lái)測(cè)試一個(gè)對(duì)象在其原型鏈中是否存在一個(gè)構(gòu)造函數(shù)的 prototype 屬性,但它不能檢測(cè)null 和 undefined

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

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

          4)constructor

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

          5)Object.prototype.toString.call()

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

          3.淺拷貝與深拷貝

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

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

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

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

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

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

          遞歸實(shí)現(xiàn)深拷貝的原理:要拷貝一個(gè)數(shù)據(jù),我們肯定要去遍歷它的屬性,如果這個(gè)對(duì)象的屬性仍是對(duì)象,繼續(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)部代碼之前)會(huì)做以下三件事:

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

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

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

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

          JavaScript 引擎創(chuàng)建了執(zhí)行棧來(lái)管理執(zhí)行上下文。可以把執(zhí)行棧認(rèn)為是一個(gè)存儲(chǔ)函數(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í)行就會(huì)創(chuàng)建函數(shù)的執(zhí)行上下文,并且把它壓入執(zhí)行棧的頂部。當(dāng)前函數(shù)執(zhí)行完成后,當(dāng)前函數(shù)的執(zhí)行上下文出棧,并等待垃圾回收。
          • 瀏覽器的JS執(zhí)行引擎總是訪問(wèn)棧頂?shù)膱?zhí)行上下文。
          • 全局上下文只有唯一的一個(gè),它在瀏覽器關(guān)閉時(shí)出棧。

          2.作用域與作用域鏈

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

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

          自由變量的值如何得到 —— 向父級(jí)作用域(創(chuàng)建該函數(shù)的那個(gè)父級(jí)作用域)尋找。如果父級(jí)也沒呢?再一層一層向上尋找,直到找到全局作用域還是沒找到,就宣布放棄。這種一層一層的關(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ù)(其他語(yǔ)言不能這樣),里面的函數(shù)可以訪問(wèn)外面函數(shù)的變量,外面的變量的是這個(gè)內(nèi)部函數(shù)的一部分。

          閉包的作用:

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

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

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

          • 函數(shù)作為參數(shù)傳遞(見作用域部分例子)
          • 函數(shù)作為返回值(如下例)
          function outer() {
           var num = 0 //內(nèi)部變量
           return function add() {
           //通過(guò)return返回add函數(shù),就可以在outer函數(shù)外訪問(wèn)了。
           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>
          

          接下來(lái)我們逐一解釋上面幾種情況

          • 對(duì)于直接調(diào)用 foo 來(lái)說(shuō),不管 foo 函數(shù)被放在了什么地方,this 一定是 window
          • 對(duì)于 obj.foo() 來(lái)說(shuō),我們只需要記住,誰(shuí)調(diào)用了函數(shù),誰(shuí)就是 this,所以在這個(gè)場(chǎng)景下 foo 函數(shù)中的 this 就是 obj 對(duì)象
          • 在構(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í)行的流程不能跨越。比如說(shuō)話后在吃飯,吃完飯后在看手機(jī),必須等待上一件事完了,才執(zhí)行后面的事情。

          異步,是一種并行處理的方式,不必等待一個(gè)程序執(zhí)行完,可以執(zhí)行其它的任務(wù)。比方說(shuō)一個(gè)人邊吃飯,邊看手機(jī),邊說(shuō)話,就是異步處理的方式。在程序中異步處理的結(jié)果通常使用回調(diào)函數(shù)來(lái)處理結(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 請(qǐng)求由于網(wǎng)絡(luò)比較慢,請(qǐng)求需要 5 秒鐘。如果是同步,這 5 秒鐘頁(yè)面就卡死在這里啥也干不了了。異步的話,就好很多了,5 秒等待就等待了,其他事情不耽誤做,至于那 5 秒鐘等待是網(wǎng)速太慢,不是因?yàn)?JS 的原因。

          3.前端異步的場(chǎng)景

          前端使用異步的場(chǎng)景

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

          4.Event Loop

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

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

          接下來(lái)我們看道例子來(lái)介紹上面流程:

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

          四、原型鏈與繼承

          1.原型和原型鏈

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

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

          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è)對(duì)象的某個(gè)屬性時(shí),如果這個(gè)對(duì)象本身沒有這個(gè)屬性,那么會(huì)去它的__proto__(即它的構(gòu)造函數(shù)的prototype)中尋找。如果一直找到最上層都沒有找到,那么就宣告失敗,返回undefined。最上層是什么 —— Object.prototype.__proto__ === null

          2.繼承

          介紹幾種常見繼承方式(如需了解更多,請(qǐng)點(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ù)中通過(guò) Parent.call(this) 繼承父類的屬性,然后改變子類的原型為 new Parent() 來(lái)繼承父類的函數(shù)。

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

          • 寄生組合繼承:這種繼承方式對(duì)上一種組合繼承進(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è)置為子類,這樣既解決了無(wú)用的父類屬性問(wèn)題,還能正確的找到子類的構(gòu)造函數(shù)。

          • ES6中class 的繼承

          ES6中引入了class關(guān)鍵字,class可以通過(guò)extends關(guān)鍵字實(shí)現(xiàn)繼承,還可以通過(guò)static關(guān)鍵字定義類的靜態(tài)方法,這比 ES5 的通過(guò)修改原型鏈實(shí)現(xiàn)繼承,要清晰和方便很多。需要注意的是,class關(guān)鍵字只是原型的語(yǔ)法糖,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)頁(yè)被加載時(shí),瀏覽器會(huì)創(chuàng)建頁(yè)面的文檔對(duì)象模型(DOM),我們可以認(rèn)為 DOM 就是 JS 能識(shí)別的 HTML 結(jié)構(gòu),一個(gè)普通的 JS 對(duì)象或者數(shù)組。接下來(lái)我們介紹常見DOM操作:

          2.DOM事件模型和事件流

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

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

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

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

          DOM事件捕獲的具體流程

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

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

          如何阻止冒泡?

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

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

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

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

          我們?cè)O(shè)定一種場(chǎng)景,如下代碼,一個(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è)事件,那對(duì)于內(nèi)存消耗是非常大的。借助事件代理,我們只需要給父容器div綁定方法即可,這樣不管點(diǎn)擊的是哪一個(gè)后代元素,都會(huì)根據(jù)冒泡傳播的傳遞機(jī)制,把父容器的click行為觸發(fā),然后把對(duì)應(yīng)的方法執(zhí)行,根據(jù)事件源,我們可以知道點(diǎn)擊的是誰(shuí),從而完成不同的事。

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

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

          4.BOM 操作

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

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

          獲取屏幕的寬度和高度

          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)然后識(shí)別客戶端,例如判斷是不是 Chrome 瀏覽器

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

          5.Ajax與跨域

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

          如何手寫 XMLHttpRequest 不借助任何庫(kù)

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

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

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

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

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

          6.存儲(chǔ)

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

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

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

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

          六、模塊化

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

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

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

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

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

          復(fù)雜的網(wǎng)站都會(huì)有大量的CSS代碼,通常也會(huì)有許多重復(fù)的值。

          舉個(gè)例子,同樣一個(gè)顏色值可能在成千上百個(gè)地方被使用到,如果這個(gè)值發(fā)生了變化,需要全局搜索并且一個(gè)一個(gè)替換,效率不高且容易出錯(cuò)。

          自定義屬性在某個(gè)地方存儲(chǔ)一個(gè)值,然后在其他許多地方引用它。另一個(gè)好處是語(yǔ)義化的標(biāo)識(shí)。比如,--main-text-color 會(huì)比 #00ff00 更易理解,尤其是這個(gè)顏色值在其他上下文中也被使用到。

          概念

          自定義屬性(有時(shí)候也被稱作CSS變量或者級(jí)聯(lián)變量)是由CSS作者定義的,它包含的值可以在整個(gè)文檔中重復(fù)使用。

          由自定義屬性標(biāo)記設(shè)定值(比如: --main-color: black;),由 var() 函數(shù)來(lái)獲取值(比如: color: **var(--main-color)**;)。

          優(yōu)勢(shì)

          在構(gòu)建大型站點(diǎn)時(shí),作者通常會(huì)面對(duì)可維護(hù)性的挑戰(zhàn)。在這些網(wǎng)頁(yè)中,所使用的CSS 的數(shù)量是非常龐大的,并且在許多場(chǎng)合大量的信息會(huì)重復(fù)使用。

          例如,在網(wǎng)頁(yè)中維護(hù)一個(gè)配色方案,意味著一些顏色在 CSS 文件中多次出現(xiàn),并被重復(fù)使用。當(dāng)你修改配色方案時(shí),不論是調(diào)整某個(gè)顏色或完全修改整個(gè)配色,都會(huì)成為一個(gè)復(fù)雜的問(wèn)題,不容出錯(cuò),而單純查找替換是遠(yuǎn)遠(yuǎn)不夠的。

          如果使用了CSS 框架,這種情況會(huì)變得尤其糟糕,此時(shí)如果要修改顏色,則需要對(duì)框架本身進(jìn)行修改。

          在這些場(chǎng)合使用 LESS 或 Sass 類似的預(yù)處理器是非常有幫助的,但是這種通過(guò)添加額外步驟的方式,可能會(huì)增加系統(tǒng)的復(fù)雜性。

          CSS變量為我們帶來(lái)一些預(yù)處理器的便利,并且不需要額外的編譯。

          這些變量的第二個(gè)優(yōu)勢(shì)就是名稱本身就包含了語(yǔ)義的信息。CSS 文件變得易讀和理解。main-text-color比文檔中的#00ff00更容易理解,特別是同樣的顏色出現(xiàn)在不同的文件中的時(shí)候。

          用法

          下面是 CSS 變量的使用方法和步驟。

          CSS 中聲明變量

          我們都知道,在 JS 中要使用一個(gè)變量前,必須聲明這個(gè)表變量。在 CSS 中也是一樣的道理。

          聲明一個(gè)自定義屬性,屬性名需要以兩個(gè)減號(hào)(--)開始,屬性值則可以是任何有效的CSS值。和其他屬性一樣,自定義屬性也是寫在規(guī)則集之內(nèi)的,如下:

          body {
              --bg-color: #7F583F;
              --color: #F7EFD2;
          }

          上面代碼中,body選擇器里面聲明了兩個(gè)變量:--bg-color和--color。

          它們與color、font-size等正式屬性沒有什么不同,只是沒有默認(rèn)含義。所以 CSS 變量(CSS variable)又叫做**"CSS 自定義屬性"**(CSS custom properties)。

          規(guī)則集所指定的選擇器定義了自定義屬性的可見作用域。通常的最佳實(shí)踐是定義在根偽類 :root下,這樣就可以在HTML文檔的任何地方訪問(wèn)到它了:

          :root {
              --main-bg-color: #eee;
          }

          自定義屬性名是大小寫敏感的,--my-color 和 --My-color 會(huì)被認(rèn)為是兩個(gè)不同的自定義屬性。

          CSS 中使用變量

          通過(guò)var()函數(shù)來(lái)讀取變量。語(yǔ)法如下:

          var(custom-property-name, value)
          • name (必需) 變量名(以兩條破折號(hào)開頭)。
          • value (可選) 表示變量的默認(rèn)值。如果該變量不存在,就會(huì)使用這個(gè)默認(rèn)值。

          變量名稱必須以兩個(gè)破折號(hào)(--)開頭,且區(qū)分大小寫!

          使用方法:

          element {
            background-color: var(--main-bg-color);
          }

          變量也可以使用在變量聲明中:

          :root {
            --primary-color: #eee;
            --primary-bg-color: var(--main-bg-color);
          }

          變量值只能用作屬性值,不能用作屬性名。

          JS 操作 CSS 變量

          在 JS 代碼中,我們可能需要讀取 CSS 變量的值,其方法如下:

          const root = document.querySelector(":root");
          // 設(shè)置 CSS 變量
          root.style.setProperty("--main-bg-color", "red");
          // 讀取 CSS 變量
          const computedStyle = getComputedStyle(root);
          const mainBgColor = computedStyle.getPropertyValue("--main-bg-color");
          console.log(mainBgColor);
          // 刪除 CSS 變量
          root.style.removeProperty("--main-bg-color");

          總結(jié)

          以上就是關(guān)于 CSS 變量的一些基本概念及使用方法,更多詳情待后續(xù)!

          靈活使用 CSS 變量,不僅可以提高生產(chǎn)力,也能夠提高代碼的可閱讀性和維護(hù)性。

          ~

          ~

          ~ 本文完

          學(xué)習(xí)有趣的知識(shí),結(jié)識(shí)有趣的朋友,塑造有趣的靈魂!

          大家好!我是〖編程三昧〗的作者 隱逸王,我的公眾號(hào)是『編程三昧』,歡迎關(guān)注,希望大家多多指教!

          知識(shí)與技能并重,內(nèi)力和外功兼修,理論和實(shí)踐兩手都要抓、兩手都要硬!


          主站蜘蛛池模板: a级午夜毛片免费一区二区| 91一区二区视频| 国产精品丝袜一区二区三区| 亚洲欧美日韩一区二区三区在线 | 怡红院美国分院一区二区| 亚洲一区二区三区国产精品无码| 国产综合一区二区在线观看| 精品人妻少妇一区二区三区| 国产乱码一区二区三区爽爽爽| 久久久久久综合一区中文字幕 | 国产成人午夜精品一区二区三区 | 无码精品不卡一区二区三区| 亚洲成在人天堂一区二区| 亚洲综合色一区二区三区| 无码人妻精品一区二区三区66 | 日本午夜精品一区二区三区电影| 亚洲熟妇av一区| 天堂资源中文最新版在线一区| 武侠古典一区二区三区中文| 久久99精品一区二区三区| 国模精品视频一区二区三区| 国产精品xxxx国产喷水亚洲国产精品无码久久一区 | 人妻aⅴ无码一区二区三区| 中文字幕一区日韩在线视频 | 中文字幕一区二区三区在线观看 | 波多野结衣一区在线| 久久精品一区二区三区不卡| 国产一区麻豆剧传媒果冻精品| 大伊香蕉精品一区视频在线| 波多野结衣AV无码久久一区| 高清国产AV一区二区三区| 99精品一区二区免费视频| 国产午夜精品片一区二区三区| 99久久精品国产高清一区二区| 国产在线精品一区免费香蕉| 亚洲av鲁丝一区二区三区| 亚洲AV无码一区二区三区在线| 无码一区二区三区在线| 亚洲乱码国产一区网址| 在线观看中文字幕一区| 亚洲国产精品成人一区|