05_javascript函數

函數 function

函數也是一個對象,

函數中可以封裝一些功能(代碼),在需要時可以執行這些功能(代碼),
函數中可以保存一些代碼在需要的時候調用

使用typeof檢查一個函數對象時,會返回function。

函數的創建:

使用構造函數來創建一個函數對象
我們在實際開發中很少使用構造函數來創建一個函數對象

//創建一個函數對象
//可以將要封裝的代碼以字符串的形式傳遞給構造函數
var fun = new Function("console.log('Hello 這是我的第一個函數');");

封裝到函數中的代碼不會立即執行,函數中的代碼會在函數調用的時候執行

調用函數語法:函數對象()
當調用函數時,函數中封裝的代碼會按照順序執行
使用 函數聲明 來創建一個函數:
語法:
	function 函數名([形參1,形參2...形參N]){
		語句...
	}

例子:
	function fun2(){
		console.log("這是我的第二個函數~~~");
		alert("哈哈哈哈哈");
		document.write("~~~~(>_<)~~~~");
	}
	fun2();// 函數中的代碼會在函數調用的時候執行
使用函數表達式來創建一個函數
語法:
	var 函數名 = function([形參1,形參2...形參N]){
		語句....
	}

例子:
	var fun3 = function(){
		console.log("我是匿名函數中封裝的代碼");
	};
	fun3();

函數的參數:

/* 定義一個用來求兩個數和的函數。*/
可以在函數的()中來指定一個或多個形參(形式參數),多個形參之間使用,隔開,
聲明形參就相當於在函數內部聲明瞭對應的變量,但是並不賦值。
function sum(a,b){
	console.log("a = "+a);
	console.log("b = "+b);
	console.log(a+b);
}

在調用函數時,可以在()中指定實參(實際參數),
實參將會賦值給函數中對應的形參。
sum(1,2);
sum(123,456);

調用函數時解析器不會檢查實參的類型,所以要注意,是否有可能會接收到非法的參數,
如果有可能則需要對參數進行類型的檢查。
// 函數的實參可以是任意的數據類型
sum(123,"hello");
sum(true , false);

調用函數時,解析器也不會檢查實參的數量,多餘實參不會被賦值;
如果實參的數量少於形參的數量,則沒有對應實參的形參將是undefined
sum(123,456,"hello",true,null);
sum(123);

函數的返回值:

可以使用 return 來設置函數的返回值。

語法:
	return 值

return後的值將會會作爲函數的執行結果返回,可以定義一個變量,來接收該結果;

在函數中return後的語句都不會執行,
如果return語句後不跟任何值就相當於返回一個undefined;
如果函數中不寫return,則也會返回undefined。

// return後可以跟任意類型的值。
function sum(a , b , c){				
	var d = a + b + c;
	return d;
}	

調用函數
變量result的值就是函數的執行結果
函數返回什麼result的值就是什麼
var result = sum(4,7,8);

立即執行函數:(IIFE)

函數定義完,立即被調用,這種函數叫做立即執行函數;
立即執行函數往往只會執行一次
(function(a,b){
	console.log("a = "+a);
	console.log("b = "+b);
})(123,456);

對象的方法:

函數也可以作爲對象的屬性,如果一個函數作爲一個對象的屬性保存,
那麼我們稱這個函數是這個對象的方法。

調用這個函數就說調用對象的方法(method),但是它只是名稱上的區別沒有其他的區別。

var obj2 = {
	name:"豬八戒",
	age:18,
	sayName:function(){
		console.log(obj2.name);
	}
};
obj2.sayName();

枚舉對象中的屬性:

使用for … in 語句
語法:
	for(var 變量 in 對象){
		...
	}

for...in語句 對象中有幾個屬性,循環體就會執行幾次,
每次執行時,會將對象中的一個屬性的名字賦值給變量。

var obj = {
	name:"孫悟空",
	age:18,
	gender:"男",
	address:"花果山"
};
for(var n in obj){
	console.log("屬性名:"+n);
	console.log("屬性值:"+obj[n]);
}

作用域:

作用域指一個變量的作用的範圍。
在JS中一共有兩種作用域:

1.全局作用域
直接編寫在script標籤中的JS代碼,都在全局作用域;

全局作用域在頁面打開時創建,在頁面關閉時銷燬;

在全局作用域中有一個全局對象window,
它代表的是一個瀏覽器的窗口,它由瀏覽器創建我們可以直接使用。

在全局作用域中:
創建的變量都會作爲window對象的屬性保存;
創建的函數都會作爲window對象的方法保存。
全局作用域中的變量都是全局變量,在頁面的任意的部分都可以訪問的到。
2.函數作用域
調用函數時創建函數作用域,函數執行完畢以後,函數作用域銷燬;

每調用一次函數就會創建一個新的函數作用域,他們之間是互相獨立的;

在函數作用域中可以訪問到全局作用域的變量;
在全局作用域中無法訪問到函數作用域的變量;

當在函數作用域操作一個變量時,它會先在自身作用域中尋找,如果有就直接使用;<br>
如果沒有則向上一級作用域中尋找,直到找到全局作用域,<br>
如果全局作用域中依然沒有找到,則會報錯ReferenceError。<br>

在函數中要訪問全局變量可以使用window對象。

在函數作用域也有聲明提前的特性,使用var關鍵字聲明的變量,
會在函數中所有的代碼執行之前被聲明。函數聲明也會在函數中所有的代碼執行之前執行。

在函數中,不使用var聲明的變量都會成爲全局變量。

定義形參就相當於在函數作用域中聲明瞭變量。

變量的聲明提前:

使用var關鍵字聲明的變量,會在所有的代碼執行之前被聲明(但是不會賦值),
但是如果聲明變量時不使用var關鍵字,則變量不會被聲明提前。

函數的聲明提前:
使用函數聲明形式創建的函數 function 函數名(){},它會在所有的代碼執行之前就被創建,
所以我們可以在函數聲明前來調用函數。
使用函數表達式創建的函數,不會被聲明提前,所以不能在聲明前調用。

JavaScript預編譯

JavaScript是解釋型語言,編譯一行,執行一行
傳統的編譯會經歷很多步驟,分詞、解析、代碼生成什麼的

JavaScript運行三部曲
語法分析 //引擎檢查你的代碼有沒有什麼低級的語法錯誤
預編譯 //在內存中開闢一些空間,存放一些變量與函數
解釋執行 //執行代碼

JS預編譯什麼時候發生
	大部分會發生在函數執行前
	
JS預編譯實例
舉例前,先來思考一下這幾個概念:
變量聲明 var… //聲明變量時不使用var關鍵字,則變量不會被聲明提前
函數聲明 function…
<script> 
	var a = 1;// 變量聲明
	function b(y){//函數聲明 
		var x = 1;
		console.log('so easy'); 
	}; 
	var c = function(){//是變量聲明而不是函數聲明!!  }
	b(100);
</script> 
<script> 
	var d = 0;
</script>

讓我們看看引擎對這段代碼做了什麼吧

頁面產生便創建了GO全局對象(Global Object)(也就是大家熟悉的window對象)
第一個腳本文件加載
腳本加載完畢後,分析語法是否合法
開始預編譯
查找函數聲明,作爲GO屬性,值賦予函數體
//僞代碼 
GO/window = {
 //頁面加載創建GO同時,創建了document、navigator、screen等等屬性,此處省略
	a: undefined, 
	c: undefined, 
	b: function(y){ 
		var x = 1; 
		console.log('so easy');
	}
 }
解釋執行代碼(直到執行函數b)
//僞代碼 
GO/window = { //變量隨着執行流得到初始化 
	a: 1, 
	c: function(){ //... },
	b: function(y){ 
		var x = 1; 
		console.log('so easy'); 
	}
}
執行函數b之前,發生預編譯
創建AO活動對象(Active Object)
查找形參和變量聲明,將變量和形參作爲AO屬性名,值賦予undefined
實參值賦給形參
查找函數聲明,值賦予函數體
//僞代碼
AO = { //創建AO同時,創建了arguments等等屬性,此處省略 
	y: 100,
	x: undefined
}
解釋執行函數中代碼
第一個腳本文件執行完畢,加載第二個腳本文件
第二個腳本文件加載完畢後,進行語法分析
語法分析完畢,開始預編譯
重複最開始的預編譯步驟……
注意:預編譯階段發生變量聲明和函數聲明,沒有初始化行爲(賦值),匿名函數不參與預編譯;
只有在解釋執行階段纔會進行變量初始化

總結:
預編譯(函數執行前)
	1. 創建AO對象(Active Object)
	2. 查找函數形參及函數內變量聲明,形參名及變量名作爲AO對象的屬性,值爲undefined。
	3. 實參形參相統一,實參值賦給形參
	4. 查找函數聲明,函數名作爲AO對象的屬性,值爲函數引用
預編譯(腳本代碼塊script執行前)
	1. 查找全局變量聲明(包括隱式全局變量聲明,省略var聲明),變量名作全局對象的屬性,值爲undefined。
	3. 查找 值爲函數引用

this:

解析器在調用函數每次都會向函數內部傳遞進一個隱含的參數,這個隱含的參數就是this,
this指向的是一個對象,這個對象我們稱爲**函數執行的上下文對象**,
根據函數的調用方式的不同,this會指向不同的對象。
	1.以函數的形式調用時,this永遠都是window
	2.以方法的形式調用時,this就是調用方法的那個對象
	3.當以構造函數的形式調用時,this就是新創建的那個對象
	4.使用call和apply調用時,this是指定的那個對象

構造函數:

構造函數就是一個普通的函數,創建方式和普通函數沒有區別,
不同的是構造函數習慣上首字母大寫。

構造函數和普通函數的區別就是調用方式的不同,普通函數是直接調用,
而構造函數需要使用new關鍵字來調用。

構造函數的執行流程:
	1.立刻創建一個新的對象;
	2.將新建的對象設置爲函數中this,在構造函數中可以使用this來引用新建的對象;
	3.逐行執行函數中的代碼
	4.將新建的對象作爲返回值返回;
	
使用同一個構造函數創建的對象,我們稱爲一類對象,也將一個構造函數稱爲一個類。

我們將通過一個構造函數創建的對象,稱爲是該類的實例。

instanceof

使用instanceof可以檢查一個對象是否是一個類的實例。

語法:
	對象 instanceof 構造函數

如果是,則返回true,否則返回false

所有的對象都是Object的後代,所以任何對象和Object作instanceof檢查時都會返回true

console.log(per instanceof Person);

原型 prototype

我們所創建的每一個函數,解析器都會向函數中添加一個屬性prototype,
這個屬性對應着一個對象,這個對象就是我們所謂的原型對象。

如果函數作爲普通函數調用prototype沒有任何作用,當函數以構造函數的形式調用時,
它所創建的對象中都會有一個隱含的屬性,指向該構造函數的原型對象,我們可以通過__proto__來訪問該屬性。

原型對象就相當於一個公共的區域,所有同一個類的實例都可以訪問到這個原型對象,
我們可以將對象中共有的內容,統一設置到原型對象中。

當我們訪問對象的一個屬性或方法時,它會先在對象自身中尋找,如果有則直接使用,
如果沒有則會去原型對象中尋找,如果找到則直接使用。
(原型對象也是對象,所以它也有原型,當我們使用一個對象的屬性或方法時,
會現在自身中尋找,自身中如果有,則直接使用,如果沒有則去原型對象中尋找,
如果原型對象中有,則使用,如果沒有則去原型的原型中尋找,直到找到Object對象的原型,
Object對象的原型沒有原型,如果在Object原型中依然沒有找到,則返回undefined 。)

我們創建構造函數時,可以將這些對象共有的屬性和方法,
統一添加到構造函數的原型對象中**,這樣不用分別爲每一個對象添加,也不會影響到全局作用域,
就可以使每個對象都具有這些屬性和方法了。

使用in檢查對象中是否含有某個屬性時,如果對象中沒有但是原型中有,也會返回true。

可以使用對象的hasOwnProperty()來檢查對象自身中是否含有該屬性。
使用該方法只有當對象自身中含有屬性時,纔會返回true。

toString:

當我們直接在頁面中打印一個對象時,
實際上是輸出的對象的toString()方法的返回值,
如果我們希望在輸出對象時不輸出[object Object],可以爲對象添加一個toString()方法。

function Person(name , age , gender){
	this.name = name;
	this.age = age;
	this.gender = gender;
}
//修改Person原型的toString
Person.prototype.toString = function(){
	Return "Person[name="+this.name+",age="+this.age+",gender="+this.gender+"]";
};

垃圾回收(GC)

就像人生活的時間長了會產生垃圾一樣,程序運行過程中也會產生垃圾
這些垃圾積攢過多以後,會導致程序運行的速度過慢,
所以我們需要一個垃圾回收的機制,來處理程序運行過程中產生垃圾。

當一個對象沒有任何的變量或屬性對它進行引用,此時我們將永遠無法操作該對象,
此時這種對象就是一個垃圾,這種對象過多會佔用大量的內存空間,
導致程序運行變慢,所以這種垃圾必須進行清理。

在JS中擁有自動的垃圾回收機制,會自動將這些垃圾對象從內存中銷燬,
我們不需要也不能進行垃圾回收的操作,我們需要做的只是要將不再使用的對象設置null即可。

call()和apply()

這兩個方法都是函數對象的方法,需要通過函數對象來調用;

當對函數調用call()和apply()都會調用函數執行;

在調用call()和apply()可以將一個對象指定爲第一個參數,此時這個對象將會成爲函數執行時的this。
call()方法可以將實參在對象之後依次傳遞;
apply()方法需要將實參封裝到一個數組中統一傳遞。

arguments

在調用函數時,瀏覽器每次都會傳遞進兩個隱含的參數:
	1.函數的上下文對象 this
	2.封裝實參的對象 arguments

arguments是一個類數組對象,它也可以通過索引來操作數據,也可以獲取長度。

在調用函數時,我們所傳遞的實參都會在arguments中保存

arguments.length可以用來獲取實參的長度
我們即使不定義形參,也可以通過arguments來使用實參,只不過比較麻煩
arguments[0] 表示第一個實參
arguments[1] 表示第二個實參 。。。

它裏邊有一個屬性叫做callee,這個屬性對應一個函數對象,就是當前正在指向的函數的對象

function fun(a,b){
	console.log(arguments instanceof Array);//false
	console.log(Array.isArray(arguments));//false
	console.log(arguments[1]);//true
	console.log(arguments.length);//2
	console.log(arguments.callee == fun);//true
}
fun("hello",true);

bind

bind() 函數會創建一個新函數(稱爲綁定函數),
新函數與被調函數(綁定函數的目標函數)具有相同的函數體(在 ECMAScript 5 規範中內置的call屬性)。 

當新函數被調用時 this 值綁定到 bind() 的第一個參數,該參數不能被重寫。

綁定函數被調用時,bind() 也接受預設的參數提供給原函數。

一個綁定函數也能使用new操作符創建對象:這種行爲就像把原函數當成構造器。
提供的 this 值被忽略,同時調用時的參數被提供給模擬函數。

this.x = 9; 
var module = {
	x: 81,
	getX: function() { return this.x; }
};
module.getX(); // 返回 81
console.log(module.getX())
var retrieveX = module.getX;
retrieveX(); // 返回 9, 在這種情況下,"this"指向全局作用域
console.log(retrieveX())

創建一個新函數,將"this"綁定到module對象
新手可能會被全局的x變量和module裏的屬性x所迷惑
var boundGetX = retrieveX.bind(module);
boundGetX(); // 返回 81 
console.log(boundGetX())

偏函數
bind()的另一個最簡單的用法是使一個函數擁有預設的初始參數。
這些參數(如果有的話)作爲bind()的第二個參數跟在this(或其他對象)後面,
之後它們會被插入到目標函數的參數列表的開始位置,傳遞給綁定函數的參數會跟在它們的後面。
function list() {
    // 將arguments(僞數組)轉爲數組返回
	return Array.prototype.slice.call(arguments);
} 
var list1 = list(1, 2, 3); // [1, 2, 3] 
// 創建一個函數擁有預設的初始參數
var leadingThirtysevenList = list.bind(undefined, 37); 
var list2 = leadingThirtysevenList(); // [37]
// 再傳參時,所傳參數會跟在預設初始參數後邊
var list3 = leadingThirtysevenList(1, 2, 3); // [37, 1, 2, 3]
//console.log(list1,list2,list3)

配合 setTimeout
在默認情況下,使用 window.setTimeout() 時,this 關鍵字會指向 window (或全局)對象。

當使用類的方法時,需要 this 引用類的實例,
你可能需要顯式地把 this 綁定到回調函數以便繼續使用實例。
function LateBloomer() {
    // 生成一個2~14之間的整數
	this.petalCount = Math.ceil(Math.random() * 12) + 1;
}
// Declare bloom after a delay of 1 second
LateBloomer.prototype.bloom = function() {
	console.log('1s後調用--')
	window.setTimeout(this.declare.bind(this), 1000);
};
LateBloomer.prototype.declare = function() {
	console.log('I am a beautiful flower with ' +
	this.petalCount + ' petals!');
};
var flower = new LateBloomer();
flower.bloom(); // 一秒鐘後, 調用'declare'方法 

模擬bind實現方法:

Function.prototype.bind = function(target){
	target = target || window
	var self = this
	var args = [].slice.call(arguments,1)
	var temp = function(){}
	var F = function(){
		var _arg = [].slice.call(arguments,0)
		return self.apply(this instanceof temp
			? this
			: target,
		args.concat(_arg))
	}
	temp.prototype = this.prototype
	F.prototype = new temp()
	return F
}

區別bind()與call()和apply()?

* 都能指定函數中的this
* call()/apply()是立即調用函數
* bind()是將函數返回 
發佈了61 篇原創文章 · 獲贊 0 · 訪問量 4326
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章