函數 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()是將函數返回