Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537 Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537
文首發自「慕課網」,想了解更多IT干貨內容,程序員圈內熱聞,歡迎關注!
作者|慕課網精英講師 然冬
變量就是存放一些內容的容器。
對于初學者,理解變量是重要的一環。
從分析變量這個名詞,可以知道他是一個可以改變的量,這里的量就是代表某一種值。
在 JavaScript 中,變量就是一個用來存放值的容器,并且可以對容器中的值做修改。
每個變量都有唯一的變量名,使用變量名來區分變量。
在 JavaScript 中使用var關鍵字來聲明變量。
var 存放數字用的變量 = 996;
console.log(存放數字用的變量); // 輸出:996
代碼塊123
上述這段代碼就是申明了一個名為存放數字用的變量的變量,并且將它的值設為996。
使用 console.log,括號內放置變量名,即可將變量的值輸出在控制臺。
其中 // 后面的內容為注釋,代碼執行過程中會被忽略。
雖然使用中文作為變量名在 chrome 瀏覽器下沒有報錯,但是還是不建議使用。
常規場景中不會有使用中文名作為變量的情況。
所以上述例子中的變量名不可取。
var number = 996;
console.log(number); // 輸出:996
代碼塊123
將存放數字用的變量修改成 number ,執行結果是一樣的。
給變量設置值的操作稱為賦值操作。
var result = 0;
console.log(result); // 輸出:0
代碼塊123
這是一個最簡單的賦值操作,直接將值賦給變量。
通常只有一個等號出現的情況下就存在賦值操作。
var result = 2 + 3;
console.log(result); // 輸出:5
代碼塊123
這也是一個賦值操作,只不過等號右邊的 2 + 3 會被計算出結果(計算的方式和小學開始學習的自然數學一樣),再賦給變量 result。
將上面這個例子做一個簡單的改寫:
var number1 = 2;
var number2 = 3;
var result = number1 + number2; // 2 + 3
console.log(result); // 輸出:5
代碼塊123456
原本 2 + 3 這部分也可以被變量所代替,參與計算的就是變量中的值。
var string = '今天加班?';
console.log(string); // 輸出:今天加班?
string = '福報!';
console.log(string); // 輸出:福報!
代碼塊1234567
注意:
這里賦給變量的值和之前有點不一樣,是中文文字。
當需要用變量存放一些“字”的時候,就需要用單引號'或者雙引號"將需要存放的字包裹。
通常單個字會稱之為字符,多個字的時候稱為字符串。
這里做個了解,具體的會在后續數據類型章節詳細展開討論。
這段代碼運行后可以在控制臺觀察到有兩個輸出,分別對應變量的值。
代碼很簡單,先聲明了一個叫 string 的變量,并賦值字符串今天加班?并輸出,隨后修改了他的值,重新賦值了字符串福報!。
這是變量最重要的一個特性:可變。
在 JavaScript 中變量名存在一定規范,所有變量名必須符合這些規范,否則程序無法執行。
盡管之前的例子有用到中文作為變量名,但是是不推薦的。
// 不會報錯但是不推薦
var 數字 = 1;
// 錯誤
var 1number = 1;
// 錯誤
var number@a = 1;
// 錯誤
var num+aa = 2;
//下面是正確的方式
var number1 = 1;
var _number = 1;
var $number = 1;
代碼塊12345678910111213
以上是一些簡單的示例,可以根據規則自己在控制臺嘗試尋找規則。
// 這是兩個不同的變量
var firstName = 'Hello';
var firstname = 'hello';
代碼塊123
以上是兩個不同的變量,在 JavaScript 中變量是對大小寫敏感的。
兩個變量名即便字母是相同的,但是大小寫不同,就不能算做一個變量。
關鍵字就是指一些已經被 JavaScript 預定義或者保留下來的內容,如聲明變量用的關鍵字 var 就不能作為變量名。
var var = 1; // Uncaught SyntaxError: Unexpected token 'var'
代碼塊1
上面這段代碼嘗試著將 var 作為變量,到控制臺運行是會報錯的。
剛開始學習的讀者,現在去深究如何命名一個變量還有些尚早,因為結合了具體的需求場景才能體會到一個好的變量名的重要性。可以先在此做個了解。
對于變量名,除了上面提到的變量命名的規范,最需要注意的就是給變量起一個有意義的名字。
如求和:
var num1 = 1;
var num2 = 2;
var num3 = 3;
var num4 = 4;
var count = num1 + num2 + num3 + num4;
代碼塊123456
其中num是number的縮寫,是很常用的一種縮寫。
count則是總數,表示求和的結果。
如果將上述例子做如下修改:
var a = 1;
var b = 2;
var c = 3;
var d = 4;
var e = a + b + c + d;
代碼塊123456
缺少了有意義的變量名就比較難看出代碼具體在做什么。當然這段代碼本身意義就不大,場景太過簡單。
剛才提到的縮寫,其實也是要注意的一點,縮寫上一定要使用通用的縮寫,如含有fn表示一個功能或者函數,avg 表示平均值,pwd 表示密碼,i18n 為國際化。
這些縮寫比較通用,大部分開發者都可以看得懂。隨著編碼經驗的增加,會在他人代碼里見到大量的縮寫,從而累積到自己的大腦的縮寫庫中。
最后需要注意的一點是業務中盡量不要含有中文拼音或中文拼音的縮寫,排開鄙視鏈的原因,最大的問題是會讓變量名變得冗長難懂。
以上內容在寫 demo 或者測試功能的時候可以不需要考慮,寫 demo 等大部分情況是為了驗證自己的猜想。
// 不合理的變量名
var ln = 'World'; // last name
var zs = 0; // 總數
var jinNianDeNianShouRu = 1999999999; // 今年的年收入
代碼塊1234
以上是針對變量名的意義展開的討論。
還有需要注意的是變量命名的格式,大部分前端程序員會使用駝峰命名法,也就是第一個字母小寫,后續如果有新的單詞來進行構成,單詞的第一個字符都大寫。
如:
var firstName = 'Hello';
var lastName = 'world';
var createAt = 1577895179196;
var userInfo = '用戶信息'; // Info => Information
var isPaidUser = '是否付費用戶';
代碼塊123456789
可以見到上面的變量,從構成變量名的第二個單詞開始,首字母都是大寫,這就是駝峰命名的格式,本 Wiki 所有變量名使用的就是這種格式。
當然還有大駝峰,就是第一個字母也大寫。
除此之外最常用的還有使用下劃線分隔變量,如 user_info,還有按功能來劃分的變量名,如使用匈牙利命名法,這里不再做展開。
變量在聲明的時候,如果沒有賦值,則變量就會有一個默認值 undefined。
var total;
console.log(total); // 輸出:undefined
代碼塊123
undefined 是一種是數據類型,具體內容可以參考數據類型章節。
使用一個 var 關鍵字就可以直接聲明多個變量。
var num1 = 0, num2 = 1;
// 通常會換行,方便閱讀代碼
var num3 = 2,
num4 = 3,
num5 = 4,
num6,
num7 = 6;
代碼塊12345678
在一個變量聲明后,使用逗號分隔,緊接著聲明下一個變量即可。
通常使用一個 var 聲明多個變量的時候也會換行,方便后續閱讀,并保持代碼格式上的整潔清晰,防止一行過長。
在最外層聲明的變量(不包括 ES6 模塊的情況),實際上是變成了 window 對象的一個屬性。
var number = 996;
console.log(number); // 輸出:996
console.log(window.number); // 輸出:996
代碼塊1234
上述代碼執行后輸出的兩個內容是一樣的,都為 996。有關 window 對象的內容可以參考 BOM 章節。
細心的讀者應該會注意到最外層這個條件,因為變量還有可能聲明在函數之中,函數有自己獨立的作用域,通常在函數中使用 var 關鍵字聲明的變量,只在函數中有效。
至于為什么可以省略 window 直接訪問到變量,可以參考作用域鏈章節。
假如不使用 var 關鍵字,直接創建變量并賦值:
total = 10;
console.log(total); // 輸出:10
代碼塊123
在控制臺運行后會發現其實并沒有報錯,輸出的結果也正常。
在非ES6模塊中,這樣創建的變量和使用 var 創建的變量除了不能提前使用之外,沒有其他大的區別,會被直接作為 window 對象的屬性,成為全局變量。
即便是在函數或者其他存在塊級作用域的環境中,這樣聲明的變量也會作為全局變量。
var a = b = 1;
代碼塊1
假如把上面這行代碼拆開來可以理解成是這樣的:
b = 1;
var a = b;
代碼塊12
看似沒什么問題,許多開發者也會用這種方式同時聲明多個變量,但如果在函數或者獨立的作用域中,b 就會成為全局變量,造成全局命名空間的污染。
按照之前說的,變量在聲明的時候如果沒有賦值,則會是 undefined,這個規則在重復聲明的情況下不適用。
var num = 1;
var num;
console.log(num); // 輸出:1
代碼塊1234
觀察上面這個例子輸出的結果,可以發現變量 num 的值并沒有改變。
但是如果重新聲明的同時做賦值操作,值就會改變。
var num = 1;
var num = 3;
console.log(num); // 輸出:3
代碼塊1234
這個例子輸出的結果,就是再次聲明并賦值后的值。
console.log(number); // 輸出:undefined
var number = 1;
代碼塊123
這個例子先輸出了 number 的值,再聲明并對其進行賦值。
代碼并沒有報錯,但如果沒有第二行聲明,只輸出 number:
console.log(number); // Uncaught ReferenceError: number is not defined
代碼塊1
這樣子會爆出變量未定義的錯誤,說明變量是可以被提前使用,只是沒有值,或者說是 undefined 默認值。
具體原因可以參考執行上下文章節。
這里簡單的解釋可以理解成,在瀏覽器執行的時候,會把代碼調整成如下樣子:
var number;
console.log(number); // 這個時候 number 還沒有被賦值,所以輸出 undefined
number = 1;
代碼塊12345
常量就是定義并賦值后再也不能修改的量,通常一些不會改變的量,如配置、物理值等會聲明為常量,在 ES6 之前是沒有提供常量這一特性的。
但是根據常量自身的特性,定義賦值后不能被修改,就可以通過一些方式來模擬常量。
第一種就是采用約定的形式,通常常量都是大寫,不同單詞之間用下劃線分隔。
var PI = 3.1415926535;
var DB_ACCOUNT = 'root';
var DB_PASSWORD = 'root';
代碼塊1234
這種方式定義的常量本質上還是變量,值還是可以修改的,但因為命名格式采用國際慣例,一眼就能看出是常量,不會對其修改。
這種方式是最簡單的方式,但不安全。
第二種方式就是利用對象下屬性的描述來控制可寫性,將對象的屬性設置為只讀。
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); // 因為不可被改寫,所以輸出:root
代碼塊123456789101112
這種方式將常量都放在一個對象下,通過Object.defineProperty定義屬性,設定其writable為false,就可以防止被改寫。
但有一個問題,CONFIG自身這個對象可能被修改。
換一個思路,既然在最外層聲明的變量是放在window上的,那可以用這個方式往 window上掛不可改寫的屬性。
Object.defineProperty(window, 'DB_ACCOUNT', {
value: 'root',
writable: false,
});
console.log(DB_ACCOUNT); // 輸出:root
DB_ACCOUNT = 'guest';
console.log(DB_ACCOUNT); // 因為不可被改寫,所以輸出:root
代碼塊12345678910
通常情況下 window 對象是不可被修改的,這樣常量的安全系數就變得非常高,但缺點是可能性較差,通過一點修改可以提升可讀性。
var define = function(name, value) {
Object.defineProperty(window, name, {
value: value,
writable: false,
});
};
define('DB_ACCOUNT', 'root');
define('DB_PASSWORD', 'root');
代碼塊123456789
只要約定好使用 define 函數定義的都為常量即可。
還有兩種方式,就是結合Object.seal與Object.freeze的特性來聲明常量。
前者可以使對象不能再被擴充,但是所有屬性還需要再手動設置不可寫,后者可以讓對象不能擴充,屬性也不能修改。
這里對這兩個原生方法不再做過多描述,有興趣可以查閱相關 API 資料。
變量就是存放值的容器。
變量名存在一些命名規則:
同時起變量名的時候需要有意義,靠近上下文場景。
歡迎關注「慕課網」,發現更多IT圈優質內容,分享干貨知識,幫助你成為更好的程序員!
接:https://juejin.im/post/5c6ad9fde51d453c356e37d1
1.JS 的數據類型分類
根據 JavaScript 中的變量類型傳遞方式,分為基本數據類型和引用數據類型。其中基本數據類型包括Undefined、Null、Boolean、Number、String、Symbol (ES6新增,表示獨一無二的值),而引用數據類型統稱為Object對象,主要包括對象、數組和函數。在參數傳遞方式上,基本類型是按值傳遞,引用類型是按共享傳遞。
題目:基本類型和引用類型的區別
基本類型和引用類型存儲于內存的位置不同,基本類型直接存儲在棧中,而引用類型的對象存儲在堆中,與此同時,在棧中存儲了指針,而這個指針指向正是堆中實體的起始位置。下面通過一個小題目,來看下兩者的主要區別:
// 基本類型 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都是引用類型。在執行了b = a之后,修改b的屬性值,a的也跟著變化。因為a和b都是引用類型,指向了同一個內存地址,即兩者引用的是同一個值,因此b修改屬性時,a的值隨之改動
2.數據類型的判斷
1)typeof
typeof返回一個表示數據類型的字符串,返回結果包括:number、boolean、string、symbol、object、undefined、function等7種數據類型,但不能判斷null、array等
2)instanceof
instanceof 是用來判斷A是否為B的實例,表達式為:A instanceof B,如果A是B的實例,則返回true,否則返回false。instanceof 運算符用來測試一個對象在其原型鏈中是否存在一個構造函數的 prototype 屬性,但它不能檢測null 和 undefined
3)嚴格運算符===
只能用于判斷null和undefined,因為這兩種類型的值都是唯一的。
4)constructor
constructor作用和instanceof非常相似。但constructor檢測 Object與instanceof不一樣,還可以處理基本數據類型的檢測。 不過函數的 constructor 是不穩定的,這個主要體現在把類的原型進行重寫,在重寫的過程中很有可能出現把之前的constructor給覆蓋了,這樣檢測出來的結果就是不準確的。
5)Object.prototype.toString.call()
Object.prototype.toString.call() 是最準確最常用的方式。
3.淺拷貝與深拷貝
淺拷貝只復制指向某個對象的指針,而不復制對象本身,新舊對象還是共享同一塊內存。
淺拷貝的實現方式(詳見淺拷貝與深拷貝):
深拷貝就是在拷貝數據的時候,將數據的所有引用結構都拷貝一份。簡單的說就是,在內存中存在兩個數據結構完全相同又相互獨立的數據,將引用型類型進行復制,而不是只復制其引用關系。
深拷貝的實現方式:
遞歸實現深拷貝的原理:要拷貝一個數據,我們肯定要去遍歷它的屬性,如果這個對象的屬性仍是對象,繼續使用這個方法,如此往復。
1.執行上下文和執行棧
執行上下文就是當前 JavaScript 代碼被解析和執行時所在環境的抽象概念, JavaScript 中運行任何的代碼都是在執行上下文中運行。 執行上下文的生命周期包括三個階段:創建階段→執行階段→回收階段,我們重點介紹創建階段。
創建階段(當函數被調用,但未執行任何其內部代碼之前)會做以下三件事:
這是因為當函數執行的時候,首先會形成一個新的私有的作用域,然后依次按照如下的步驟執行:
函數多了,就有多個函數執行上下文,每次調用函數創建一個新的執行上下文,那如何管理創建的那么多執行上下文呢?
JavaScript 引擎創建了執行棧來管理執行上下文。可以把執行棧認為是一個存儲函數調用的棧結構,遵循先進后出的原則。
2.作用域與作用域鏈
ES6 到來JavaScript 有全局作用域、函數作用域和塊級作用域(ES6新增)。我們可以這樣理解:作用域就是一個獨立的地盤,讓變量不會外泄、暴露出去。也就是說作用域最大的用處就是隔離變量,不同作用域下同名變量不會有沖突。 在介紹作用域鏈之前,先要了解下自由變量,如下代碼中,console.log(a)要得到a變量,但是在當前的作用域中沒有定義a(可對比一下b)。當前作用域沒有定義的變量,這成為 自由變量。
var a = 100 function fn() { var b = 200 console.log(a) // 這里的a在這里就是一個自由變量 console.log(b) } fn()
自由變量的值如何得到 —— 向父級作用域(創建該函數的那個父級作用域)尋找。如果父級也沒呢?再一層一層向上尋找,直到找到全局作用域還是沒找到,就宣布放棄。這種一層一層的關系,就是作用域鏈 。
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的值,從函數F1中查找而不是F2,這是因為當自由變量從作用域鏈中去尋找,依據的是函數定義時的作用域鏈,而不是函數執行時。
3.閉包是什么
閉包這個概念也是JavaScript中比較抽象的概念,我個人理解,閉包是就是函數中的函數(其他語言不能這樣),里面的函數可以訪問外面函數的變量,外面的變量的是這個內部函數的一部分。
閉包的作用:
閉包不能濫用,否則會導致內存泄露,影響網頁的性能。閉包使用完了后,要立即釋放資源,將引用變量指向null。
閉包主要有兩個應用場景:
function outer() { var num = 0 //內部變量 return function add() { //通過return返回add函數,就可以在outer函數外訪問了。 num++ //內部函數有引用,作為add函數的一部分了 console.log(num) } } var func1 = outer() // func1() //實際上是調用add函數, 輸出1 func1() //輸出2 var func2 = outer() func2() // 輸出1 func2() // 輸出2
4.this全面解析
先搞明白一個很重要的概念 —— this的值是在執行的時候才能確認,定義的時候不能確認! 為什么呢 —— 因為this是執行上下文環境的一部分,而執行上下文需要在代碼執行之前確定,而不是定義的時候。看如下例子:
// 情況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是當前類的一個實例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">箭頭函數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>
接下來我們逐一解釋上面幾種情況
1.同步 vs 異步
同步,我的理解是一種線性執行的方式,執行的流程不能跨越。比如說話后在吃飯,吃完飯后在看手機,必須等待上一件事完了,才執行后面的事情。
異步,是一種并行處理的方式,不必等待一個程序執行完,可以執行其它的任務。比方說一個人邊吃飯,邊看手機,邊說話,就是異步處理的方式。在程序中異步處理的結果通常使用回調函數來處理結果。
// 同步 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 是單線程運行的,即在同一時間只能做一件事,不能“一心二用”。為了利用多核CPU的計算能力,HTML5提出Web Worker標準,允許JavaScript腳本創建多個線程,但是子線程完全受主線程控制,且不得操作DOM。所以,這個新標準并沒有改變JavaScript單線程的本質。
一個 Ajax 請求由于網絡比較慢,請求需要 5 秒鐘。如果是同步,這 5 秒鐘頁面就卡死在這里啥也干不了了。異步的話,就好很多了,5 秒等待就等待了,其他事情不耽誤做,至于那 5 秒鐘等待是網速太慢,不是因為 JS 的原因。
3.前端異步的場景
前端使用異步的場景
4.Event Loop
一個完整的 Event Loop 過程,可以概括為以下階段:
接下來我們看道例子來介紹上面流程:
Promise.resolve().then(()=>{ console.log('Promise1') setTimeout(()=>{ console.log('setTimeout2') },0) }) setTimeout(()=>{ console.log('setTimeout1') Promise.resolve().then(()=>{ console.log('Promise2') }) },0)
最后輸出結果是Promise1,setTimeout1,Promise2,setTimeout2
1.原型和原型鏈
原型:在JavaScript中原型是一個prototype對象,用于表示類型之間的關系。
原型鏈:JavaScript萬物都是對象,對象和對象之間也有關系,并不是孤立存在的。對象之間的繼承關系,在JavaScript中是通過prototype對象指向父類對象,直到指向Object對象為止,這樣就形成了一個原型指向的鏈條,專業術語稱之為原型鏈。
var Person = function() { this.age = 18 this.name = '匿名' } var Student = function() {} //創建繼承關系,父類實例作為子類原型 Student.prototype = new Person() var s1 = new Student() console.log(s1)
原型關系圖:
當試圖得到一個對象的某個屬性時,如果這個對象本身沒有這個屬性,那么會去它的__proto__(即它的構造函數的prototype)中尋找。如果一直找到最上層都沒有找到,那么就宣告失敗,返回undefined。最上層是什么 —— Object.prototype.__proto__ === null
2.繼承
介紹幾種常見繼承方式(如需了解更多,請點擊JavaScript常見的六種繼承方式):
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
以上繼承的方式核心是在子類的構造函數中通過 Parent.call(this) 繼承父類的屬性,然后改變子類的原型為 new Parent() 來繼承父類的函數。
這種繼承方式優點在于構造函數可以傳參,不會與父類引用屬性共享,可以復用父類的函數,但是也存在一個缺點就是在繼承父類函數的時候調用了父類構造函數,導致子類的原型上多了不需要的父類屬性,存在內存上的浪費。
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
以上繼承實現的核心就是將父類的原型賦值給了子類,并且將構造函數設置為子類,這樣既解決了無用的父類屬性問題,還能正確的找到子類的構造函數。
ES6中引入了class關鍵字,class可以通過extends關鍵字實現繼承,還可以通過static關鍵字定義類的靜態方法,這比 ES5 的通過修改原型鏈實現繼承,要清晰和方便很多。需要注意的是,class關鍵字只是原型的語法糖,JavaScript繼承仍然是基于原型實現的。
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 實現繼承的核心在于使用 extends 表明繼承自哪個父類,并且在子類構造函數中必須調用 super,因為這段代碼可以看成 Parent.call(this, value)。
1.DOM操作
當網頁被加載時,瀏覽器會創建頁面的文檔對象模型(DOM),我們可以認為 DOM 就是 JS 能識別的 HTML 結構,一個普通的 JS 對象或者數組。接下來我們介紹常見DOM操作:
2.DOM事件模型和事件流
DOM事件模型分為捕獲和冒泡。一個事件發生后,會在子元素和父元素之間傳播(propagation)。這種傳播分成三個階段。
(1)捕獲階段:事件從window對象自上而下向目標節點傳播的階段;
(2)目標階段:真正的目標節點正在處理事件的階段;
(3)冒泡階段:事件從目標節點自下而上向window對象傳播的階段。
DOM事件捕獲的具體流程
捕獲是從上到下,事件先從window對象,然后再到document(對象),然后是html標簽(通過document.documentElement獲取html標簽),然后是body標簽(通過document.body獲取body標簽),然后按照普通的html結構一層一層往下傳,最后到達目標元素。
接下來我們看個事件冒泡的例子:
如何阻止冒泡?
通過event.stopPropagation() 方法阻止事件冒泡到父元素,阻止任何父事件處理程序被執行。 我們可以在上例中inner元素的click事件上,添加event.stopPropagation()這句話后,就阻止了父事件的執行,最后只打印了'inner'。
inner.onclick = function(ev) { console.log('inner') ev.stopPropagation() }
3.事件代理(事件委托)
由于事件會在冒泡階段向上傳播到父節點,因此可以把子節點的監聽函數定義在父節點上,由父節點的監聽函數統一處理多個子元素的事件。這種方法叫做事件的代理。
我們設定一種場景,如下代碼,一個<div>中包含了若干個<a>,而且還能繼續增加。那如何快捷方便地為所有<a>綁定事件呢?
<div id="div1"> <a href="#">a1</a> <a href="#">a2</a> <a href="#">a3</a> <a href="#">a4</a> </div> <button>點擊增加一個 a 標簽</button>
如果給每個<a>標簽一一都綁定一個事件,那對于內存消耗是非常大的。借助事件代理,我們只需要給父容器div綁定方法即可,這樣不管點擊的是哪一個后代元素,都會根據冒泡傳播的傳遞機制,把父容器的click行為觸發,然后把對應的方法執行,根據事件源,我們可以知道點擊的是誰,從而完成不同的事。
最后,使用代理的優點如下:
4.BOM 操作
BOM(瀏覽器對象模型)是瀏覽器本身的一些信息的設置和獲取,例如獲取瀏覽器的寬度、高度,設置讓瀏覽器跳轉到哪個地址。
獲取屏幕的寬度和高度
console.log(screen.width) console.log(screen.height)
獲取網址、協議、path、參數、hash 等
// 例如當前網址是 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
另外,還有調用瀏覽器的前進、后退功能等
history.back() history.forward()
獲取瀏覽器特性(即俗稱的UA)然后識別客戶端,例如判斷是不是 Chrome 瀏覽器
var ua = navigator.userAgent var isChrome = ua.indexOf('Chrome') console.log(isChrome)
5.Ajax與跨域
Ajax 是一種異步請求數據的一種技術,對于改善用戶的體驗和程序的性能很有幫助。 簡單地說,在不需要重新刷新頁面的情況下,Ajax 通過異步請求加載后臺數據,并在網頁上呈現出來。常見運用場景有表單驗證是否登入成功、百度搜索下拉框提示和快遞單號查詢等等。Ajax的目的是提高用戶體驗,較少網絡數據的傳輸量。
如何手寫 XMLHttpRequest 不借助任何庫
因為瀏覽器出于安全考慮,有同源策略。也就是說,如果協議、域名或者端口有一個不同就是跨域,Ajax 請求會失敗。
那么是出于什么安全考慮才會引入這種機制呢? 其實主要是用來防止 CSRF 攻擊的。簡單點說,CSRF 攻擊是利用用戶的登錄態發起惡意請求。
然后我們來考慮一個問題,請求跨域了,那么請求到底發出去沒有? 請求必然是發出去了,但是瀏覽器攔截了響應。
常見的幾種跨域解決方案:
6.存儲
sessionStorage 、localStorage 和 cookie 之間的區別
作用域:localStorage只要在相同的協議、相同的主機名、相同的端口下,就能讀取/修改到同一份localStorage數據。sessionStorage比localStorage更嚴苛一點,除了協議、主機名、端口外,還要求在同一窗口(也就是瀏覽器的標簽頁)下
生命周期:localStorage 是持久化的本地存儲,存儲在其中的數據是永遠不會過期的,使其消失的唯一辦法是手動刪除;而 sessionStorage 是臨時性的本地存儲,它是會話級別的存儲,當會話結束(頁面被關閉)時,存儲內容也隨之被釋放。
幾種常見模塊化規范的簡介:
CommonJS規范主要用于服務端編程,加載模塊是同步的,這并不適合在瀏覽器環境,因為同步意味著阻塞加載,瀏覽器資源是異步加載的,因此有了AMD CMD解決方案
AMD規范在瀏覽器環境中異步加載模塊,而且可以并行加載多個模塊。不過,AMD規范開發成本高,代碼的閱讀和書寫比較困難,模塊定義方式的語義不順暢。
CMD規范與AMD規范很相似,都用于瀏覽器編程,依賴就近,延遲執行,可以很容易在Node.js中運行。不過,依賴SPM 打包,模塊的加載邏輯偏重
ES6 在語言標準的層面上,實現了模塊功能,而且實現得相當簡單,完全可以取代 CommonJS 和 AMD 規范,成為瀏覽器和服務器通用的模塊解決方案
復雜的網站都會有大量的CSS代碼,通常也會有許多重復的值。
舉個例子,同樣一個顏色值可能在成千上百個地方被使用到,如果這個值發生了變化,需要全局搜索并且一個一個替換,效率不高且容易出錯。
自定義屬性在某個地方存儲一個值,然后在其他許多地方引用它。另一個好處是語義化的標識。比如,--main-text-color 會比 #00ff00 更易理解,尤其是這個顏色值在其他上下文中也被使用到。
自定義屬性(有時候也被稱作CSS變量或者級聯變量)是由CSS作者定義的,它包含的值可以在整個文檔中重復使用。
由自定義屬性標記設定值(比如: --main-color: black;),由 var() 函數來獲取值(比如: color: **var(--main-color)**;)。
在構建大型站點時,作者通常會面對可維護性的挑戰。在這些網頁中,所使用的CSS 的數量是非常龐大的,并且在許多場合大量的信息會重復使用。
例如,在網頁中維護一個配色方案,意味著一些顏色在 CSS 文件中多次出現,并被重復使用。當你修改配色方案時,不論是調整某個顏色或完全修改整個配色,都會成為一個復雜的問題,不容出錯,而單純查找替換是遠遠不夠的。
如果使用了CSS 框架,這種情況會變得尤其糟糕,此時如果要修改顏色,則需要對框架本身進行修改。
在這些場合使用 LESS 或 Sass 類似的預處理器是非常有幫助的,但是這種通過添加額外步驟的方式,可能會增加系統的復雜性。
CSS變量為我們帶來一些預處理器的便利,并且不需要額外的編譯。
這些變量的第二個優勢就是名稱本身就包含了語義的信息。CSS 文件變得易讀和理解。main-text-color比文檔中的#00ff00更容易理解,特別是同樣的顏色出現在不同的文件中的時候。
下面是 CSS 變量的使用方法和步驟。
我們都知道,在 JS 中要使用一個變量前,必須聲明這個表變量。在 CSS 中也是一樣的道理。
聲明一個自定義屬性,屬性名需要以兩個減號(--)開始,屬性值則可以是任何有效的CSS值。和其他屬性一樣,自定義屬性也是寫在規則集之內的,如下:
body {
--bg-color: #7F583F;
--color: #F7EFD2;
}
上面代碼中,body選擇器里面聲明了兩個變量:--bg-color和--color。
它們與color、font-size等正式屬性沒有什么不同,只是沒有默認含義。所以 CSS 變量(CSS variable)又叫做**"CSS 自定義屬性"**(CSS custom properties)。
規則集所指定的選擇器定義了自定義屬性的可見作用域。通常的最佳實踐是定義在根偽類 :root下,這樣就可以在HTML文檔的任何地方訪問到它了:
:root {
--main-bg-color: #eee;
}
自定義屬性名是大小寫敏感的,--my-color 和 --My-color 會被認為是兩個不同的自定義屬性。
通過var()函數來讀取變量。語法如下:
var(custom-property-name, value)
變量名稱必須以兩個破折號(--)開頭,且區分大小寫!
使用方法:
element {
background-color: var(--main-bg-color);
}
變量也可以使用在變量聲明中:
:root {
--primary-color: #eee;
--primary-bg-color: var(--main-bg-color);
}
變量值只能用作屬性值,不能用作屬性名。
在 JS 代碼中,我們可能需要讀取 CSS 變量的值,其方法如下:
const root = document.querySelector(":root");
// 設置 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");
以上就是關于 CSS 變量的一些基本概念及使用方法,更多詳情待后續!
靈活使用 CSS 變量,不僅可以提高生產力,也能夠提高代碼的可閱讀性和維護性。
~
~
~ 本文完
學習有趣的知識,結識有趣的朋友,塑造有趣的靈魂!
大家好!我是〖編程三昧〗的作者 隱逸王,我的公眾號是『編程三昧』,歡迎關注,希望大家多多指教!
知識與技能并重,內力和外功兼修,理論和實踐兩手都要抓、兩手都要硬!
*請認真填寫需求信息,我們會在24小時內與您取得聯系。