web前端開發筆記整理(三)


以下更得是JavaScript函數以及閉包等內容,有需要的可參考前兩篇博客,這裏主要記得是HTML和css部分這裏主要記的是js最基礎部分


函數

函數是由事件驅動的或者當它被調用時執行的可重複使用的代碼塊。

  • 函數就是包裹在花括號中的代碼塊,前面使用了關鍵詞 function,函數名functionname
function functionname()
{
    // 執行代碼
}
  • 當調用該函數時,會執行函數內的代碼。
  • 可以在某事件發生時直接調用函數(比如當用戶點擊按鈕時),並且可由 JavaScript 在任何位置進行調用。
  • function必須小寫
  • 函數聲明的方式總的有兩種,一種是函數聲明,一種是函數表達式,雖然都可以聲明函數,但其實作用是不一樣的

帶參數的函數

  • 向函數傳值,這些值被稱爲參數
  • 參數用,分隔,參數可在函數內使用
  • 參數在函數內不要var,直接寫入
    變量和參數必須以一致的順序出現。第一個變量就是第一個被傳遞的參數的給定的值。
<button onclick="myFunction('Harry Potter','Wizard')">點擊這裏</button>
<script>
function myFunction(name,job){
    alert("Welcome " + name + ", the " + job);
}
</script>

在這裏插入圖片描述

return

  • 通過return語句將函數值返回調用他的地方
  • 使用return時,函數會停止執行,並返回指定的值
function functionName(target){
	//隱式調用 +自動轉成number型
	return +target;
	
}
var num=functionName('123');
console.log(typeof(num)+" "+num); 

在這裏插入圖片描述
僅僅希望退出函數時

function myFunction(a,b)
{
    if (a>b)
    {
        return;
    }
    x=a+b
}

如果 a 大於 b,則上面的代碼將退出函數,並不會計算 a 和 b 的總和
插入幾個小練習:

  • 寫一個函數。告知你選定的小動物叫聲
function scream(animal){
	switch(animal){
		case "dog":
			document.write('wang');
			return;
		case "cat":
			document.write("miao");
			return;
			}
	}

在這裏插入圖片描述

  • 實現加法計數器功能
function add() {
	var sum = 0;
			for (var i = 0; i < add.arguments.length; i++) {
					sum += add.arguments[i];
				}
				return sum;
			}
alert(add(143,51,21,97));

在這裏插入圖片描述

arguments.length爲函數實參個數
這裏瞭解一下arguments的用法

  • 定義·一組函數,輸入數字,逆轉輸出漢字形式
function sum(){
	var num = window.prompt("請輸入你的值:");

	var str = "";

   	for (var i = num.length-1;i >= 0; i--) {
   	    str	+=transfer(num[i]);
	}

    document.write(str);
} 

function transfer(target){
	switch(target){
		case "1":
		  return "壹";
		case "2":
		  return "貳";
	    case "3":
		  return "弎";
		case "4":
		  return "四";
		case "5":
		  return "伍";
		case "6":
		  return "陸";
		case "7":
		  return "七";
		case "8":
		  return "八";
		case "9":
		  return "玖";
	}
}

sum();

在這裏插入圖片描述在這裏插入圖片描述

  • 寫一個函數,實現n的階乘
function n(x) {
            if(x == 1) {
                return 1;
            } else {
                return x * n(x - 1);
            }
        }
       
        alert(n(5));

在這裏插入圖片描述

作用域

作用域是可訪問變量的集合。

  • 在 JavaScript 中, 對象和函數同樣也是變量。
  • 在 JavaScript 中, 作用域爲可訪問變量,對象,函數的集合。
  • JavaScript 函數作用域: 作用域在函數內修改。
局部變量

變量在函數內聲明,變量爲局部作用域,只能在函數內部訪問。
局部變量在函數開始執行時創建,函數執行完後局部變量會自動銷燬。

// 此處不能調用 carName 變量
function myFunction() {
    var carName = "Volvo";
    // 函數內可調用 carName 變量
}
全局變量

變量在函數外定義,即爲全局變量。全局變量有 全局作用域: 網頁中所有腳本和函數均可使用。

var carName = " Volvo";
 
// 此處可調用 carName 變量
function myFunction() {
    // 函數內可調用 carName 變量
}

如果變量在函數內沒有聲明(沒有使用 var 關鍵字),該變量爲全局變量。

// 此處可調用 carName 變量
 
function myFunction() {
    carName = "Volvo";
    // 此處可調用 carName 變量
}
變量生命週期
  • JavaScript 變量生命週期在它聲明時初始化。
  • 局部變量在函數執行完畢後銷燬。
  • 全局變量在頁面關閉後銷燬。
HTML 中的全局變量
預編譯
  • js運行時會進行三件事:1語法分析 2.預編譯 3.解釋執行

  • 語法分析會在代碼執行前對代碼進行通篇檢查,以排除一些低級錯誤

  • 預編譯發生在函數執行的前一刻
    小知識穿插:

imply global 暗示全局變量:即任何變量,如果變量未經聲明就賦值,此變量就爲全局對象(window)所有,所有數據變量都屬於 window 對象
我們先不定義直接賦值

a=123

此時a會被認爲是全局的一個對象,即window所屬的值
相當於

`window.b  =123;`

window就是全局
我們這裏聲明一個變量a並賦值

var a=123

相當於

windows.a=123;

也就是

a=123

對預編譯的淺顯理解:
在這裏插入圖片描述

  • 在函數執行前函數聲明(function(){})會整體提升至邏輯的最前方
 <script>
   test();
   functiont  test(){
   console.log('a');

  }
</script>

輸出的結果爲: a

  • 變量的聲明提升到函數調用前面,不是賦值,只是聲明
 <script>
  
    console.log(a);
    var a = 123;
    
</script>

輸出結果爲:undefined;

  • 函數聲明才存在變量提升
function a(){};,
var b =function (){};//不會提升

預編譯的四部曲:

    1.創建AO對象,Activation Object,函數執行生成了存儲空間庫
    2.找形參和變量聲明,將變量和形參名作爲AO對象屬性名,值爲undefined
    3.將實參值和形參統一
    4.在函數體裏面找函數聲明,值賦予函數體

重要性是4>3>2>1
舉個例,形參變量實參一樣情況下

function fn (a) {
  console.log(a);

  var a = 123;

  console.log(a);
  
  function a(){};

  console.log(a);

  var b =function (){};

  console.log(b);
}
fn(1);

1.創建AO對象,我們隱式的在函數中創建了一個AO的對象來盛放函數中的變量,此時對象中並沒有值;

AO{ 
}

2:找形參和變量聲明,將變量和形參名作爲AO()屬性名,值爲undefined

AO{
 a:undefined
 b:undefined
}

3.將實參值和形參值統一,此時將實參帶入函數中,fn(1),因此AO中a 爲 1

AO{
 a:1 
 b:undefined
}

4.在函數體裏找函數聲明,值賦予函數體,由於在函數中有 function a() {} ,這一函數因此此時AO中 a = function a() {}

AO{
  a:function a(){}
  b:undefined
  d:function d(){}
}

預編譯完後進行執行:

  • 一句一句執行,執行第一句console.log(a);那麼,會在AO對象中調取a,在AO對象中a等於functiona(){},那麼就輸出function a(){} 函數聲明
  • var a = 123,var a 已經被提升了,所以不用管,直接執行a = 123,所以,在AO對象中,a變成了123
  • AO對象變成123後,執行console.log(a);現在a等於123,那麼就是輸出123,
  • 第四句是函數,由於函數已經被提升了,跳過
  • 第五句是console.(a),所以輸出還是123
  • 第六句是var b = function (){},所以就要把在AO對象中的b的屬性改爲function(){}
  • 第七句b的輸出就是function(){},第八句直接被提升了,所以不用讀了

結果爲:

function a(){}
123
123
function (){}

全局預編譯:
生成GO對象 global object GO==window

<script>
    global = 100;
    function fn(){
    console.log(global);
    global=200;
    console.log(global);
    var global=300;
}
    fn();
    var global;
</script>

結果爲:

undefined
200

執行過程:

  • 首先生成 global:undefined
  • 執行第一行:global = 100
  • 執行fn()時生成AO{ global = undefined; }
  • 先生成GO再生成AO
    js是塊編譯,即一個script塊中預編譯然後執行,再按順序預編譯下一個script塊再執行
    此時上一個script塊中的數據都是可用的了,而下一個塊中的函數和變量則是不可用的。
立即執行函數

原理: 只有表達式才能被執行
沒有函數聲明,在執行一次過後即被釋放,除了這點與其他函數沒有區別,適合做初始化工作。
立即執行函數也有預編譯過程;
立即執行函數寫法:

(function (){
}())  //推薦
(function  (){
})()

函數會將函數變成表達式

function test (){
var a=1;
}

我們在調用test的時候可直接使用
在這裏插入圖片描述
加個括號

function test (){
var a=1;
}()

在這裏插入圖片描述
這是函數聲明,因爲只有表達式才能夠被(括號)執行
而上面的式子是函數聲明,不是表達式。

var test = function(){
console.log(123");
}()

這就是函數表達式,能被立即執行符號執行的表達式,再被立即執行之後,就變成了立即執行函數,執行完就銷燬

添個加號或者-號就成表達式了:

+ function test (){
document.write("abc");
}()

在這裏插入圖片描述

- , !,&&(要能運行到函數) ,|| ,“+”
加上這些符號就變成了立即執行函數

括號也算數學表達式
所以:

(function functionname (){
})

函數聲明加上一對括號變成函數表達式

(function functionName (){
})()

加上一對括號讓他執行。
把括號放在裏面。

(function functionName (){
console.log('a');
}())

先執行外面的括號,變成表達式,然後再執行裏面的表達式
根據立即執行函數的定義,發現functionName在使用後已經沒有意義了;
在這裏插入圖片描述
接着我們去掉函數名,就是立即執行函數了

形式如下:由下圖可見執行完之後函數已經被銷燬了

 (function functionName(){
	var a=1;
	var b=2;
    document.write(a+b);
	
}())

在這裏插入圖片描述
與其他函數一樣,可以傳遞實參和形參

(function functionName(a,b){
    document.write(a+b);
	
}(1,2))

在這裏插入圖片描述

var num =(function test(a,b){
	var c;
	c=a+b;
	return c;
}(1,2))

在這裏插入圖片描述

閉包

下面是幾個理解:
閉包是將函數內部和函數外部連接起來的橋樑

閉包就是能夠讀取其他函數內部變量的函數。例如在javascript中,只有函數內部的子函數才能讀取局部變量,所以閉包可以理解成定義在一個函數內部的函數。在本質上,閉包是將函數內部和函數外部連接起來的橋樑。

Javascript允許使用內部函數—即函數定義和函數表達式位於另一個函數的函數體內
這些內部函數可以訪問它們所在的外部函數中聲明的所有局部變量、參數和聲明的其他內部函數。
當其中一個這樣的內部函數在包含它們的外部函數之外被調用時,就會形成閉包。

只要將內部函數傳遞到所在的詞法作用域以外,它都會持有對原始作用域的引用,無論在何處執行這個函數都會使用閉包。

// JavaScript Document
function test(){
        var arr = [];
        for(var i = 0 ; i<10;i++)
        {
              arr[i] = function(){
                    document.write(i);
              }
        }
        return arr;
    }
    var  myArr =  test();
    for(var j = 0 ; j<10;j++)
        {
              myArr[j]();//結果爲10個10
        }

輸出
在這裏插入圖片描述
記一下要注意的幾點
函數體不是立刻執行,函數引用不打印函數體內語句也就是說 arr[i] = function(){
document.write(i); 執行位置不是他現在定義的位置,而是在函數調用的地方 myArrj;
當函數調用完成纔會執行輸出i
在函數外部可調用內部函數 return 函數與test()形成了閉包 都能用test()ao執行上下文arr隨着for循環變化,for循環會產生十組數據,也就是內部函數執行10次。
十次結束後returnarr,將十組數據保存在外部,在外部執行,所以10個方法的i是共用的,在執行完test()後,此時的i相當於緩存在myArr中,且存入的值是test()的AO銷燬前值,在銷燬之前i存儲的值是10
利用閉包解決閉包

那我們怎麼輸出0-9呢

function test(){
        var arr = [];
        for(var i = 0 ; i<10;i++)
        {
            (function(j){//此處隱式使用閉包的存儲功能,存儲的內容爲j的數據
              arr[j] = function(){
                    document.write(j);
               }
            }(i));//使用立即執行函數的實參,當執行myArr[i]時,查詢的變量i即就轉化爲對應的立即執行函數的形參k
        }
        return arr;
    }
    var  myArr =  test();
    for(var j = 0 ; j<10;j++)
        {
              myArr[j]();
        }

在這裏插入圖片描述
這裏我們引入立即執行函數,循環十次的同時每個函數保存在arr裏面的j都不同。
立即執行函數讀到馬上就會執行,讀後銷燬是相對於引用銷燬,值依然被保留。

閉包將內部函數作爲返回值輸出,然後用一個變量去接收外部函數的調用結果,即外部函數的返回值,通過調用這個變量就可以調用該函數了,如果是返回多個函數,則將其存於數組中,根據數組下標找到對應的函數。

找了幾個小練習再理解一下

<ul>
    <li>1</li>
    <li>2</li>
    <li>3</li>
</ul>
function test(){
var lis = document.getElementsByTagName("li");
for (var i = 0; i < lis.length; i++) {
(function(j){
    lis[i].onclick = function() {
        console.log(i);
    }
   }(i))
 }
}
test();

這段代碼本來像實現的功能是點擊li標籤輸出的值爲點擊li的順序值,但是結果是每次輸出都是3…
其實這個跟上段代碼有相同之處,我們知識給li標籤定義了點擊屬性,但是函數體不會立刻執行。
只有點擊li時,函數執行,函數中的變量i沒有在函數中定義,根據js的作用域鏈原則,會繼續向上級作用域查詢,因此找到了全局作用域中的i,這時for循環已經執行結束,此時全局作用域中的i已經變爲了3,故打印出來的當然是3

我們同樣用閉包解決這個問題

var lis = document.getElementsByTagName("li");
for (var i = 0; i < lis.length; i++) {
    lis[i].onclick = (function(i) {
       var clickLi = function() { 
           console.log(i);         
       }
       return clickLi;
    })(i)
}

在for循環執行時,立即將當前的i值作爲形參傳入clickLi中,而形參默認爲函數內的局部變量,函數外部是不能對i進行操作的。所以,當點擊li時,執行clickLi函數時,打印出來的則是li的順序值。

閉包的用途

  • 可以重複使用變量,並且不會造成變量污染

  • 全局變量可以重複使用,但是容易造成變量污染。局部變量僅在局部作用域內有效,不可以重複使用,不會造成變量污染。閉包結合了全局變量和局部變量的優點。

  • 可以用來定義私有屬性和私有方法。

使用閉包的注意點

  • 比普通函數更佔用內存,會導致網頁性能變差,在IE下容易造成內存泄露。

什麼是內存泄露

首先,需要了解瀏覽器自身的內存回收機制。
每個瀏覽器會有自己的一套回收機制,當分配出去的內存不使用的時候便會回收;內存泄露的根本原因就是你的代碼中分配了一些‘頑固的’內存,瀏覽器無法進行回收,如果這些’頑固的’內存還在一直不停地分配就會導致後面所用內存不足,造成泄露。

閉包造成內存泄漏

因爲閉包就是能夠訪問外部函數變量的一個函數,而函數是必須保存在內存中的對象,所以位於函數執行上下文中的所有變量也需要保存在內存中,這樣就不會被回收,如果一旦循環引用或創建閉包,就會佔據大量內存,可能會引起內存泄漏

內存泄漏的解決方案

造成內存泄露的原因:

  • 意外的全局變量(在函數內部沒有使用var進行聲明的變量)

  • console.log

  • 閉包

  • 對象的循環引用

  • 未清除的計時器

  • DOM泄露(獲取到DOM節點之後,將DOM節點刪除,但是沒有手動釋放變量,拿對應的DOM節點在變量中還可以訪問到,就會造成泄露)

閉包的使用場景

閉包的應用場景
閉包極其應用場景

😃

再看一道題

var f=(
function f(){
	return "1";
},
	function g(){
		return 2;
	}
)();
console.log(typeof(f));

問輸出什麼?
在這裏插入圖片描述

逗號運算符,它將第一個的參數,再計算第二個的參數值。然後返回最第二個參數的值
在看一個

var x=1;
if (function f() {} )){
x+=typeof f;
}
console.log(x);

輸出 1undefined
if條件裏面能執行,括號裏面函數就成爲表達式,不是函數定義 f就沒得了
任何一個變量未經定義放在typeof不報錯 返回字符串類型undefined
要不是字符串類型加起來就是非數 NaN

在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章