涉及到的鏈接: W3school-JavaScript教程 JavaScript簡介 文檔對象模型 (DOM) JavaScript BOM(瀏覽器對象模型) JS面向對象之原型 JavaScript基於原型的面向對象編程 js的變數(詳解) 關於JavaScript作用域的理解 簡單談談JavaS ...
涉及到的鏈接:
JS——從入門到入獄(doge)
JavaScript簡介
JavaScript是腳本語言,是一種輕量級的編程語言,插入HTML頁面後可由所有現代瀏覽器執行;
由布蘭登·艾克(Brendan Eich,Mozilla 項目、Mozilla 基金會和 Mozilla 公司的聯合創始人)發明。
JavaScript 相當簡潔,卻非常靈活。開發者們基於 JavaScript 核心編寫了大量實用工具,可以使 開發工作事半功倍。其中包括:
- 瀏覽器應用程式介面(API)—— 瀏覽器內置的 API 提供了豐富的功能,比如:動態創建 HTML 和設置 CSS 樣式、從用戶的攝像頭採集處理視頻流、生成 3D 圖像與音頻樣本等等。
- 第三方 API —— 讓開發者可以在自己的站點中整合其它內容提供者(Twitter、Facebook 等)提供的功能。
- 第三方框架和庫 —— 用來快速構建網站和應用。
JavaScript的組成部分
ECMAscript
ECMAscript是JavaScript的核心,其規定了JS的語法規範
最近常用的一套規範為ECMAScript 6,也就是我們常說的ES6,隨著ES的版本迭代,JavaScript也會更優化
有關ES6的新特性會在之後說
DOM
Document Object Model(文檔對象模型)
DOM 模型用一個邏輯樹來表示一個文檔,樹的每個分支的終點都是一個節點 (node),每個節點都包含著對象 (objects)。DOM 的方法 (methods) 讓你可以用特定方式操作這個樹,用這些方法你可以改變文檔的結構、樣式或者內容。節點可以關聯上事件處理器,一旦某一事件被觸發了,那些事件處理器就會被執行。
開始的時候,JavaScript 和 DOM 是交織在一起的,但它們最終演變成了兩個獨立的實體。JavaScript 可以訪問和操作存儲在 DOM 中的內容,因此我們可以寫成這個近似的等式:
API (web 或 XML 頁面) = DOM + JS (腳本語言) 只是近似
Application Programming Interface(應用程式介面)
有關API的知識,我的暫時理解就是介面——供開發人員為了實現某個功能而去調用的、無需訪問源碼、無需理解其內部工作機制的東西
總之DOM會將web頁面和腳本或者程式語言連接起來
API會在之後詳細介紹
BOM
Browser Object Model(瀏覽器模型對象)
BOM模型提供了獨立與內容的、可以與瀏覽器視窗進行互動的對象結構,BOM由多個對象構成,其中代表瀏覽器視窗的window對象是BOM的頂層對象,其他對象都是該對象的子對象。
window對象
Window 對象表示瀏覽器中打開的視窗。
如果文檔包含框架(frame 或 iframe 標簽),瀏覽器會為 HTML 文檔創建一個 window 對象,併為每個框架創建一個額外的 window 對象。
window常用的對象屬性
- pageXOffset——設置或返回當前頁面相對於視窗顯示區左上角的 X 位置。
- pageYOffset——設置或返回當前頁面相對於視窗顯示區左上角的 Y 位置。
screenLeft,screenTop,screenX,screenY 聲明瞭視窗的左上角在屏幕上的的 x 坐標和 y 坐標。
IE、Safari 和 Opera 支持 screenLeft 和 screenTop,而 Firefox 和 Safari 支持 screenX 和 screenY。
window對象常用方法
- onload()——當頁面完全載入時,觸發該事件
- onscroll()——當視窗滾動時觸發該事件
- onresize()——當視窗大小發生變化時觸發該事件
- setInterval()——按照指定的周期(以毫秒計)來調用函數或計算表達式
- setTimeout()——在指定的毫秒數後調用函數或計算表達式
- open()——打開一個新的瀏覽器視窗或查找一個已命名的視窗
Location對象
用於獲得當前頁面的地址 (URL),並把瀏覽器重定向到新的頁面
location對象常用的屬性
- location.herf = 'url地址'
- location.host——返回伺服器名稱和埠號
- location.pathname——返回目錄和文件名
- location.search 返回?號後面的所有值
- location.portocol 返回頁面使用的協議, http:或https
location對象常用的方法
- href——設置或獲取整個 URL 為字元串
- reload()——重新載入頁面地址。
- replace()——重新定向URL,不會在歷史記錄中生成新紀錄
Navigator對象
navigator 對象包含有關訪問者瀏覽器的信息
navigator對象常用的屬性
- navigator.platform——操作系統類型;
- navigator.userAgent——瀏覽器設定的User-Agent字元串
- navigator.appName——瀏覽器名稱;
- navigator.appVersion——瀏覽器版本;
- navigator.language——瀏覽器設置的語言;
- navigator.userAgent——最常用的屬性,用來完成瀏覽器判斷
screen對象
window.screen 對象包含有關用戶屏幕的信息
history對象
history 對象包含瀏覽器的歷史。為了保護用戶隱私,對 JavaScript 訪問該對象的方法做出了限制。
history對象常用的方法
- history.back() - 載入歷史列表中的前一個 URL。返回上一頁。
- history.forward() - 載入歷史列表中的下一個 URL。返回下一頁。
- history.go(“參數”) -1表示上一頁,1表示下一頁,或者具體頁面的URL。
JavaScript的面向對象編程(JS OOP)
JavaScript所面向的對象,是一種基於原型的面向對象,與傳統的面向對象有區別;
基於原型的語言(如 JavaScript)並不存在這種區別:它只有對象。基於原型的語言具有所謂原型對象 (prototypical object)的概念。原型對象可以作為一個模板,新對象可以從中獲得原始的屬性。任何對象都可以指定其自身的屬性,既可以是創建時也可以在運行時創建。而且,任何對象都可以作為另一個對象的原型 (prototype),從而允許後者共用前者的屬性。
對此我們需要理解原型。
原型(prototype)
JavaScript中幾乎所有的對象都有一個原型對象,這個原型對象指向他的父對象,我們可以通過對象的proto屬性訪問到自身的原型對象。
事實上在JavaScript中,當訪問一個對象的屬性時,對象會先從自身的屬性中查找,如果沒有就往自身的原型對象中查找,再沒在再上一級,直到找到該屬性或者找到最頂層才停止。這一點就很像CSS
構造器
JavaScript中,一種定義對象的方法就是通過調用構造器方法。構造器本身是一個普通的函數,但是當調用構造器時在前面加上new關鍵字時,它就成了一個構造器函數。
let Student = function(name, number) {
this.name = name;
this.number = number;
console.log('Student in');
}
let student1 = new Student('張三', 1234); //Student in
let student2 = new Student('李四', 5678); //Student in
console.log('學生1是:' + student1.name + ' 編號是:' + student1.number); //學生1是:張三 編號是:1234
console.log('學生2是:' + student2.name + ' 編號是:' + student2.number); //學生2是:李四 編號是:5678
繼承
JavaScript 通過將構造器函數與原型對象相關聯的方式來實現繼承。
基於類(Java)和基於原型(JavaScript)的對象系統的比較
基於類的(Java) | 基於原型的(JavaScript) |
---|---|
類和實例是不同的事物。 | 所有對象均為實例。 |
通過類定義來定義類;通過構造器方法來實例化類。 | 通過構造器函數來定義和創建一組對象。 |
通過 new 操作符創建單個對象。 |
相同。 |
通過類定義來定義現存類的子類,從而構建對象的層級結構。 | 指定一個對象作為原型並且與構造函數一起構建對象的層級結構 |
遵循類鏈繼承屬性。 | 遵循原型鏈繼承屬性。 |
類定義指定類的所有實例的所有屬性。無法在運行時動態添加屬性。 | 構造器函數或原型指定實例的初始屬性集。允許動態地向單個的對象或者整個對象集中添加或移除屬性。 |
JavaScript中的變數
變數是什麼?
變數是用於存儲數據的抽象空間,變數的本身並不是數據,而是裝載數據的一個容器,變數使得電腦有了記憶。
你對‘蘋果’的認識相當於數據,儲存這些數據——形狀,價格,味道等等——的空間就是變數
變數聲明?
變數聲明相當於告訴程式我要創建一個存儲空間了,像是一個申請,根據聲明關鍵詞的不同,所申請的空間也不同,JavaScript中有三個變數聲明以及一個無聲明的形式。
JavaScript作用域
全局作用域、局部作用域
JavaScript的作用域為全局或者局部基本要靠function來區分,寫於函數體內變數其作用域為局部作用域,作用範圍僅在函數的大括弧內;
聲明於全局的變數即為全局變數,其無論在哪裡——全局或者函數體內——都可以使用;
局部作用域在變數提升時不適用
作用域鏈
全局作用域和局部作用域中變數的訪問許可權,其實是由作用域鏈決定的。
每次進入一個新的執行環境,都會創建一個用於搜索變數和函數的作用域鏈。作用域鏈是函數被創建的作用域中對象的集合。作用域鏈可以保證對執行環境有權訪問的所有變數和函數的有序訪問。
作用域鏈的最前端始終是當前執行的代碼所在環境的變數對象(如果該環境是函數,則將其活動對象作為變數對象),下一個變數對象來自包含環境(包含當前還行環境的環境),下一個變數對象來自包含環境的包含環境,依次往上,直到全局執行環境的變數對象。全局執行環境的變數對象始終是作用域鏈中的最後一個對象。
標識符解析是沿著作用域一級一級的向上搜索標識符的過程。搜索過程始終是從作用域的前端逐地向後回溯,直到找到標識符(找不到,就會導致錯誤發生)。
總結:
- 每次進入一個新的執行環境,都會創建一個用於搜索變數和函數的作用域鏈
- 執行環境有全局執行環境(全局環境)和局部執行環境之分
- 執行環境決定了變數的生命周期,以及哪部分代碼可以訪問其中變數
- 函數的局部環境可以訪問函數作用域中的變數和函數,也可以訪問其父環境,乃至全局環境中的變數和環境
- 全局環境只能訪問全局環境中定義的變數和函數,不能直接訪問局部環境中的任何數據
JavaScript的變數聲明
var
var聲明的變數可以不初始化賦值,其輸出為undefined。
var聲明的變數的作用域是全局或者是函數級的,定義於函數體內其作用域即為函數級;
var聲明無法被單單的大括弧限制其作用域;
var語句多次聲明一個變數不僅是合法的,而且也不會造成任何錯誤;如果重覆使用的一個聲明有一個初始值,那麼它擔當的不過是一個賦值語句的角色;如果重覆使用的一個聲明沒有一個初始值,那麼它不會對原來存在的變數有任何的影響;
let
let需要JavaScript的嚴格模式:’use strict‘
let不能重覆聲明
let不能夠預處理,也就是必須要賦初值,不然會報錯
let聲明的變數作用域是局部的,僅可函數內部使用
const
onst定義的變數不可以修改,而且必須初始化。關鍵字 const 有一定誤導性;它不定義常量數組。它定義的是對數組的常量引用,所以數組元素仍舊可以修改
該變數是個全局變數,或者是模塊內的全局變數;可以在全局作用域或者函數內聲明常量,但是必須初始化常量
如果一個變數只有在聲明時才被賦值一次,永遠不會在其它的代碼行里被重新賦值,那麼應該使用const,但是該變數的初始值有可能在未來會被調整(常變數)
創建一個只讀常量,在不同瀏覽器上表現為不可修改;建議聲明後不修改;擁有塊級作用域
const 代表一個值的常量索引 ,也就是說,變數名字在記憶體中的指針不能夠改變,但是指向這個變數的值可能 改變
const定義的變數不可修改,一般在require一個模塊的時候用或者定義一些全局常量
常量不能和它所在作用域內其它變數或者函數擁有相同名稱
JavaScript的變數(函數)提升
JS的引擎機製為先編譯在執行,編譯過程中,編譯會根據聲明為其確定作用域,然後基於順序為每一個語句進行編譯、執行。
變數提升說的是var
一般來說,我們使用變數會經過“聲明、賦值、使用”的流程,但是JS的變數或者函數由於聲明提升可以先使用後聲明
test();
function test() {
a = 1;
console.log(a);
var a = 2;
console.log(a);
}
/*
輸出:
1
2
*/
我們知道函數體的第一個a的聲明一定提升了,但是提升到了哪裡?是提升到了函數的第一行還是函數體外成為了全局變數?來慢慢探索
function test() {
a = 1;
console.log(a);
var a = 2;
console.log(a);
}
test();
console.log(a);
/*
輸出:
1
2
Uncaught ReferenceError: a is not defined
*/
這裡由於重定義了a成為了塊級作用域變數,導致在函數體外無法輸出
test();
function test() {
a = 1;
console.log(a);
// var a = 2;
// console.log(a);
}
console.log(a);
/*
輸出:
1
1
*/
test();
function test() {
var a;
a = 1;
console.log(a);
// var a = 2;
// console.log(a);
}
console.log(a);
/*
輸出:
1
Uncaught ReferenceError: a is not defined
*/
就此我們發現a的聲明提升到了函數體外,成為了全局變數
變數(函數)提升的好處
提高性能
在JS代碼執行之前,會進行語法檢查和預編譯,並且這一操作只進行一次。這麼做就是為了提高性能,如果沒有這一步,那麼每次執行代碼前都必須重新解析一遍
該變數(函數),這是沒有必要的,因為變數(函數)的代碼並不會改變,解析一遍就夠了。
在解析的過程中,還會為函數生成預編譯代碼。在預編譯時,會統計聲明瞭哪些變數、創建了哪些函數,並對函數的代碼進行壓縮,去除註釋、不必要的空白等。
這樣做的好處就是每次執行函數時都可以直接為該函數分配棧空間(不需要再解析一遍去獲取代碼中聲明瞭哪些變數,創建了哪些函數),並且因為代碼壓縮的原
因,代碼執行也更快了。
容錯性提高
減少了由於疏忽導致的報錯
規範的減少導致容錯率提升,對我來說這不算好事
變數(函數)提升的壞處
增大空間的占用
變數提升會延長變數的生命周期,增大空間的占用
這個很好解釋,全局變數的生命周期比局部變數的生命周期長,而變數提升會使原局部變數成為全局變數,導致函數結束時,本該結束生命周期、被銷毀的變數仍舊存在
不易維護
由於變數提升很容易會將變數覆蓋,導致後期的維護會變得困難
禁用變數提升
能夠靈活使用變數提升很好,但是仍舊不推薦經常使用變數提升
let和const
為瞭解決上述問題,ES6 引入了 let 和 const 關鍵字,從而使 JavaScript 也能像其他語言一樣擁有塊級作用域。let 和 const 是不存在變數提升的
use strict模式
嚴格模式的寫法就是在代碼編寫之前加上"use strict",嚴格模式要求你不能使用未聲明的變數,否則會報錯。
還是建議大家儘可能得使用嚴格模式來編寫javascript代碼,以消除Javascript語法的一些不合理、不嚴謹之處,讓自己成為一個更優秀的程式員。
JavaScript的數據類型
原始類型(基本數據類型)
存儲空間大小一定
Number
- 整數與浮點數——1、3.14
- 八進位數與十六進位數——0768、0xff
- 指數——1e+5
- Infinity與NaN
- Infinity:表示一個超出JavaScript接受範圍的數字,相當於無窮大,無窮小則為 -Infinity
- NaN:表示的使一種不符合規範但是仍舊屬於數字類型的數字,例如當一個字元串與數字相乘時,返回的便是NaN,即Not a Number
String
String 是字元串類型,這一類型的數據主要指的是被反引號、單引號或雙引號所引起來的、由任意個字元組成的字元序列
雖然單引號和雙引號都可以使用,出於代碼可讀性方面的考慮,建議字元串風格保持一致,不要單引號和雙引號交叉使用
ES6新增了反引號,也就是波浪線按鍵,用於簡歷模板字元串,特定場景會使用到
Boolean
Boolean 是布爾類型,這一類型的數據只有 true 和 false 兩種值,主要用於關係運算和邏輯運算
undefined
這是 JavaScript 中的一個特殊值,當我們訪問一個不存在的變數或未被初始化的變數時,程式就會返回一個 undefined 值
null
這也是 JavaScript 中的一個特殊值,通常是指沒有值或值為空,不代表任何東西
引用類型(對象)
在JavaScript中,除了以上的基本數據類型,其他所有的值都被視為對象,某些環境會將null值視為一個對象
引用類型的數據占用記憶體空間的大小是不固定的,JavaScript解釋器會在堆空間里為其分配記憶體,而不是棧,從而避免降低數據的存儲速度
關於JavaScript中的this
每創建一個變數對象都會自動地創建一個執行環境。有關執行環境的內容在JavaScript在JavaScript中的變數——JavaScript作用域——作用域鏈
作用域鏈其實就相當於JavaScript中的執行環境鏈,而this的指向就是執行this語句的執行環境,其加上JavaScript的提升(JavaScript中的變數——JavaScript的變數提升),導致JS中的this相當魔性……(此說法有待考證)
其實可以將this的綁定視為以下幾種情況
預設綁定
當一個函數沒有明確的調用對象的時候,也就是單純作為獨立函數調用的時候,將對函數的this使用預設綁定:綁定到全局的window對象
凡是函數作為獨立函數調用,無論它的位置在哪,它行為表現都和直接在全局環境中調用無異
隱式綁定
當函數被一個對象所“包含”的時候,我們稱函數的this被隱式綁定到這個對象裡面,這時候,通過this可以直接訪問所綁定的對象裡面的其他屬性
顯示綁定
能夠改變this指向的call()、apply()以及bind()
call()
call 是函數原型上的方法,所有的實例都可以調用;
call會立即執行函數
call的實現原理:call會將需要改變this的函數掛載到context對象的臨時方法上,然後調用context的臨時方法,此時this就會指向context,最後刪除掉臨時方法
Function.prototype.newCall = function (context,...arg) {
const ctx = context || window; // 沒傳或者傳null預設是window
ctx.fun = this; // 將call方法的目標函數掛載到目標對象身上
const res = ctx.fun(...args);// 執行fun並傳參,fun是contex調用的
delete context.fun; // 刪除fun
return res
}
function test(name) {
console.log(name + 'is' + this.age + 'years old');
}
const obj = { age: 20 }
test.newCall(obj, 'pillow');// pillow is 20 years old
apply()
apply 的實現原理和call相同,僅僅是傳參的方式不同,並且傳入的是一個數組
Function.prototype.newApply = function (context, arr=[]) {
const ctx = context || window;
ctx.fun = this // 將apply方法的目標函數掛到目標對象身上
const res = ctx.fun(...arr);
delete context.fun;
return res;
}
bind()
bind不會立即執行函數,所以需要返回一個待執行的函數從而產生閉包
Function.prototype.myBind= function(bindObj){
var _this = this,
slice = Array.prototype.slice,
args = slice.apply(arguments,[1]);
return function(){
//apply綁定作用域,進行參數傳遞
return bindObj.apply(that,args)
}
}
var test = function(a,b){
console.log('作用域綁定 '+ this.value)
console.log('testBind參數傳遞 '+ a.value2)
console.log('調用參數傳遞 ' + b)
}
var obj = {value:'ok'}
var newFn = test.myBind(obj,{value2:'also ok'})
newFn ('hello bind')
// 作用域綁定 ok
// testBind參數傳遞 also ok
// 調用參數傳遞 undefined
Web API
待補充
Ajax
即Asynchronous JavaScript and XML(非同步的 JavaScript 和 XML)
傳統頁面如頁面內容有更改,則需要刷新整個頁面,而Ajax可以局部刷新網頁
ajax主要是實現頁面和web伺服器之間數據的非同步傳輸。
簡單來說,不採用ajax的頁面,當用戶在頁面發起請求時,就要進行整個頁面的刷新,刷新快慢取決於伺服器的處理快慢。在這個過程中用戶必須得等待,不能進行其他操作。也就是同步的方式。客戶端和服務端傳遞了很多不需要的數據。效率低,用戶體驗差。
XML
XMLHttpRequest(XHR)對象用於與伺服器交互,我們通過 XMLHttpRequest 可以在不刷新頁面的情況下請求特定 URL獲取數據,並且雖然名字叫XMLHttpRequest,但實際上可以用於獲取任何類型的數據。
……
同步和非同步
為什麼會有同步和非同步
因為JavaScript的單線程,因此同個時間只能處理同個任務,所有任務都需要排隊,前一個任務執行完,才能繼續執行下一個任務,但是,如果前一個任務的執行時間很長,比如文件的讀取操作或ajax操作,後一個任務就不得不等著,拿ajax來說,當用戶向後臺獲取大量的數據時,不得不等到所有數據都獲取完畢才能進行下一步操作,用戶只能在那裡乾等著,嚴重影響用戶體驗因此,JavaScript在設計的時候,就已經考慮到這個問題,主線程可以完全不用等待文件的讀取完畢或ajax的載入成功,可以先掛起處於等待中的任務,先運行排在後面的任務,等到文件的讀取或ajax有了結果後,再回過頭執行掛起的任務,因此,任務就可以分為同步任務和非同步任務
同步任務
同步任務是指在主線程上排隊執行的任務,只有前一個任務執行完畢,才能繼續執行下一個任務,當我們打開網站時,網站的渲染過程,比如元素的渲染,其實就是一個同步任務
非同步任務
非同步任務是指不進入主線程,而進入任務隊列的任務,只有任務隊列通知主線程,某個非同步任務可以執行了,該任務才會進入主線程,當我們打開網站時,像圖片的載入,音樂的載入,其實就是一個非同步任務
非同步機制
所有同步任務都在主線程上執行,行成一個執行棧
主線程之外,還存在一個任務隊列,只要非同步任務有了結果,就會在任務隊列中放置一個事件
一旦執行棧中的所有同步任務執行完畢,系統就會讀取任務隊列,看看裡面還有哪些事件,那些對應的非同步任務,於是結束等待狀態,進入執行棧,開始執行
主線程不斷的重覆上面的第三步
Promise規範
一個promise可能有三種狀態:等待(pending)、已完成(fulfilled)、已拒絕(rejected)
一個promise的狀態只可能從“等待”轉到“完成”態或者“拒絕”態,不能逆向轉換,同時“完成”態和“拒絕”態不能相互轉換
promise必須實現then方法(可以說,then就是promise的核心),而且then必須返回一個promise,同一個promise的then可以調用多次,並且回調的執行順序跟它們被定義時的順序一致
then方法接受兩個參數,第一個參數是成功時的回調,在promise由“等待”態轉換到“完成”態時調用,另一個是失敗時的回調,在promise由“等待”態轉換到“拒絕”態時調用,同時,then可以接受另一個promise傳入,也接受一個“類then”的對象或方法,即thenable對象