JavaScript基礎--------三座大山(前端面試必考)
1.原型和原型鏈
2.作用域和閉包
3.非同步和單執行緒
被稱為JavaScript的三座大山
原型和原型鏈:
在JavaScript中,陣列,物件和函式被稱為引用型別,他們都有一個__proto__屬性,該屬性是一個物件(我們稱之為隱式原型)
arr陣列的建構函式是Array,Array建構函式中有一個prototype屬性,(我們暫時稱之為顯式原型)
arr是建構函式的例項物件,arr中的__proto__物件指向建構函式中的prototype物件
接下來是一個簡單的demo
1 //建立一個建構函式 2function Animal(name){ 3this.name = name 4} 5Animal.prototype.eat = function(){ 6console.log('Animal--eat') 7} 8// 用new初始化一個Animal的例項物件 9var dog = new Animal('xiaohuang') 10 11console.log(dog.name) 12console.log(dog.eat())
輸出結果為
看一下dog物件中有什麼屬性
呼叫dog的屬性和方法時,會先從dog本身去查詢,如果dog本身沒有那個屬性或方法,就會去dog的__proto__原型中去查詢,而__proto__又指向Animal的prototype(看第二個constructor物件,指向Animal),這就是原型鏈
再來一個demo
1 //建立一個建構函式 2function Animal(name){ 3this.name = name 4} 5//建立一個Hashiqi的建構函式 6function Hashiqi(){ 7} 8Animal.prototype.eat = function(){ 9console.log('Animal--eat') 10} 11Hashiqi.prototype.color = function(){ 12console.log('Hashiqi---color') 13} 14//改變prototype的指向 15Animal.prototype = new Hashiqi() 16// 用new初始化一個Animal的例項物件 17var dog = new Animal('xiaohuang') 18 19console.log(dog.name) 20console.log(dog.eat()) 21console.log(dog.color())
這個demo中改變了Animal的prototype,將其指向一個Hashiqi的例項物件,我們來看結果。此時dog.eat()會報錯,dog.color正常輸出
第三個demo,比較上檔次一點
1 <div id="wrapper">this is wrapper</div> 2<script> 3//建立一個Elem的建構函式 4function Elem(id){ 5//獲取dom元素 6this.id = document.getElementById(id) 7} 8Elem.prototype.html = function(val){ 9//如果val為空,則列印dom元素的innerhtml值 10if(val == null){ 11console.log(this.id.innerHTML) 12//返回this,可以用來進行鏈式操作 13return this 14}else{ 15this.id.innerHTML = val 16return this 17} 18} 19//繫結事件 20Elem.prototype.on = function(type, fn){ 21this.id.addEventListener(type,fn) 22} 23 24</script>
首先new一個例項物件:var el = new Elem('wrapper')
el.html(' ookook ').on('click', function(){ console.log('this is ook') } )
接下來點選ookook就會列印this is ook
作用域和閉包:
JavaScript中有函式作用域和全域性作用域(es6中用let宣告的變數具有塊作用域)
函式作用域是在一個函式中有效,全域性作用域在全域性都有效
*函式內部如果變數和全域性變數重名了,則在該函式內部,以函式變數為準
*函式外部無法訪問函式內部定義的變數(該變數是函式私有的),不過函式內部可以訪問函式外部的全域性變數
1.變數提升
javascript中宣告並定義一個變數時,會把宣告提前,以下會先打印出undefined,再打印出10
1 console.log(a) 2var a = 10 3console.log(a)
相當於
1var a 2console.log(a) 3a = 10 4console.log(a)
函式宣告也是,以下函式相當於把整個fn提到作用域的最上面,所以呼叫fn時會正常列印jack
1 fn('jack') 2function fn (name){ 3console.log(name) 4}
不過函式表示式不行,以下是一個函式表示式,JavaScript會把var fn提到作用域最上面,沒有吧函式提上去,所以會報錯
1 fn("jack"); 2 3var fn = function(name) { 4console.log(name); 5};
2.閉包
使用場景:1.函式作為引數傳遞2.函式作為返回值傳遞
三言兩語說不清楚,我們來看一個demo
1 function F1(){ 2var a = 100 3//返回一個函式 4return function(){ 5console.log(a) 6} 7} 8 9var a = 200; 10var f1 = F1(); //將f1指向F1 11f1()
第11行輸出結果是100
第11行呼叫f1的時候要列印a變數,return的函式中沒有a變數,所以a是個自由變數,要去 宣告 該函式(不是呼叫)的父級作用域去查詢,即functin F1中,a=100
閉包在開發中的應用
以下函式的目的是:當_list中沒有val值時,返回true並把val新增到_list中
這個demo使用了閉包,在外部無法直接訪問_list這個Fn函式的私有變數,這樣可以保證資料不被汙染,提高了安全性
1 function Fn(){ 2var _list = [] 3 4return function(val){ 5if(_list.indexOf(val) < 0){ 6_list.push(val) 7return true 8}else{ 9return false 10} 11} 12} 13 14var f1 = Fn() 15console.log(f1(10))//true 16console.log(f1(10))//false 17console.log(f1(20))//true 18console.log(f1(20))//false
非同步和單執行緒
非同步和單執行緒是相輔相成的,js是一門單執行緒指令碼語言,所以需要非同步來輔助
非同步和同步的區別: 非同步會阻塞程式的執行,同步不會阻塞程式的執行,
比如只有在執行完alert後才會列印100,如果你不去點選彈框的確定鍵,console.log就永遠不會執行,這就是同步的阻塞
第二個demo中,會馬上就列印100,兩秒後再列印setTimeout,這就是非同步 不會阻塞 程式執行
1alert('ook') 2console.log(100)
1 setTimeout(function(){ 2console.log('setTimeout') 3},2000) 4console.log(100)
在哪些場景中會非同步執行
JavaScript中以下三種情況會非同步執行 1.定時任務:setTimeout, setInterval2.網路請求:ajax請求,動態載入<img>圖示3.事件繫結:比如‘click’等
看一個demo
以下函式的目的是在頁面中建立5個a標籤,點選第一個a標籤列印1,點選第二個a標籤列印2,以此類推。可執行結果都是5,為什麼呢?
因為click是一個非同步事件,計算機不知道使用者什麼時候會點選,所以不能夠是同步,不然使用者不點選,程式就永遠無法往下執行。
非同步事件會先拿出來放到一個佇列裡,等同步事件執行完了,再來執行非同步事件
所以當你點選a標籤的時候,i已經迴圈到5了(i是全域性變數。這也涉及到了js作用域:該函式沒有定義i變數,就要去宣告該函式的父級作用域中去找,而不是呼叫)
1 for(var i = 0; i < 5; i++){ 2//建立一個a標籤 3var a = document.createElement('a') 4a.innerHTML = i + 1 + '<br>' 5//給a標籤繫結click事件 6a.addEventListener('click', function(e){ 7e.preventDefault() 8console.log(i) 9}) 10//將a標籤新增到wrpper中 11document.querySelector('#wrapper').appendChild(a) 12}
解決這個問題就可以用閉包,用一個function把給a新增時間的地方包起來,(function(i){})(i) 這是函式的自呼叫。注意:自呼叫前要加分號,也就是第4行結束要加分號,不然js會分不清何時開始自呼叫,會報錯
1for(var i = 0; i < 5; i++){ 2//建立一個a標籤 3var a = document.createElement('a') 4a.innerHTML = i+1 + '<br>'; 5//給a標籤繫結click事件 6(function(i){ 7a.addEventListener('click', function(e){ 8e.preventDefault() 9console.log(i+1) 10}) 11})(i) 12//將a標籤新增到wrpper中 13document.querySelector('#wrapper').appendChild(a) 14}