說起ECMAScript中什么最有意思,我想那莫過于函數(shù)了——而有意思的根源疲扎,則在于函數(shù)實(shí)際上是對(duì)象纪隙。每個(gè)函數(shù)都是Function類型的實(shí)例,而且都與其他引用類型一樣具有屬性和方法等恐。由于函數(shù)是對(duì)象,因此函數(shù)名實(shí)際上也是一個(gè)指向函數(shù)對(duì)象的指針,不會(huì)與某個(gè)函數(shù)綁定课蔬。
如何理解函數(shù)是對(duì)象囱稽?
在JavaScript基礎(chǔ)知識(shí)點(diǎn)解讀—對(duì)象這篇文章的后半部分已經(jīng)介紹過JavaScript中函數(shù)和對(duì)象之間的關(guān)系。
創(chuàng)建函數(shù)
有三種基本的函數(shù)創(chuàng)建方式二跋。
// 1 使用function聲明
function add(num1, num2) {
return num1 + num2;
}
// 2 函數(shù)表達(dá)式
var add = function(num1, num2) {
return num1 + num2;
}
// 3 使用new Function創(chuàng)建
var add = new Function("num1", "num2", "return num1 + num2"); //不推薦
創(chuàng)建函數(shù)的時(shí)候一定要理解在JavaScript中函數(shù)沒有重載战惊。
什么是重載?
面向?qū)ο缶幊陶Z(yǔ)言的三大基本特征:封裝同欠,繼承和多態(tài)。
實(shí)現(xiàn)多態(tài)的兩種方式:
覆蓋(重寫)横缔,子類重新定義父類中的虛函數(shù)
重載铺遂,是指允許存在多個(gè)同名函數(shù)茎刚,而這些函數(shù)的參數(shù)表不同(或許參數(shù)個(gè)數(shù)不同襟锐,或許參數(shù)類型不同,或許兩者都不同)膛锭。
更多的面向?qū)ο笳Z(yǔ)言的特性就不做更多的解釋粮坞。一定要記住JavaScript中沒有重載。為什么初狰?因?yàn)槭褂胒unction聲明的函數(shù)后面聲明的函數(shù)會(huì)把前面聲明的函數(shù)覆蓋掉莫杈,同一個(gè)函數(shù)名只保留一個(gè)最后一個(gè)定義的函數(shù)體。使用var聲明的函數(shù)表達(dá)同樣存在覆蓋的情況奢入,看下面例子筝闹。
// 1 使用function聲明函數(shù)
function addSomeNumber(num) {
return num + 100;
}
function addSomeNumber(num) {
return num + 200;
}
var result = addSomeNumber(100); // 300
// 使用函數(shù)表達(dá)式
var addSomeNumber = function(num) {
return num + 100;
};
addSomeNumber = function(num) {
return num + 100;
};
var result = addSomeNumber(100); // 300
函數(shù)聲明和函數(shù)表達(dá)式
思考!函數(shù)聲明和函數(shù)表達(dá)式有什么區(qū)別腥光?
函數(shù)聲明有函數(shù)提升的作用关顷,即解析器會(huì)率先讀取函數(shù)聲明,并使其在執(zhí)行任何代碼之前可用(可以訪問)武福。至于函數(shù)表達(dá)式议双,則必須等到解析器執(zhí)行到它所在的代碼行,才會(huì)真正被解釋執(zhí)行捉片。
// 函數(shù)聲明
sum(10, 10); // 20
function sum(num1, num2) {
return num1 + num2;
}
// 函數(shù)表達(dá)式
sum(10, 10); // 報(bào)錯(cuò) Uncaught TypeError: sum is not a function
var sum = function(num1, num2) {
return num1 + num2;
}
作為值的函數(shù)
在ECMAScript中平痰,函數(shù)名本身就是變量,所以函數(shù)也可以作為值來使用伍纫。比如下面的程序觉增。
function callSomeFunction(someFn, someArgument) {
return someFn(someArgument);
}
function add10(num) {
return num + 10;
}
var result = callSomeFunction(add10, 10);
console.log(result); // 20
這里記住函數(shù)名也是變量,和普通變量的區(qū)別是在函數(shù)名變量后面加上“()”后可以執(zhí)行即可翻斟。
函數(shù)內(nèi)部屬性
在函數(shù)內(nèi)部有兩個(gè)特殊的對(duì)象:arguments和this逾礁。
arguments,是一個(gè)類數(shù)組對(duì)象,它里面存的是傳入函數(shù)中的所有參數(shù)嘹履。這個(gè)對(duì)象還有一個(gè)名叫callee的屬性腻扇,該屬性是一個(gè)指針,指向擁有這個(gè)arguments對(duì)象的函數(shù)砾嫉。請(qǐng)看下面這個(gè)非常經(jīng)典的階乘函數(shù)幼苛。
function factorial(num) {
if(num <= 1) {
return 1;
} else {
return num * factorial(num - 1);
}
}
定義階乘函數(shù)一般都要用到遞歸算法;如上面的代碼所示焕刮,在函數(shù)有名字且名字以后也不會(huì)變的情況下舶沿,這樣定義沒有問題。問題是這個(gè)函數(shù)的執(zhí)行與函數(shù)名factorial緊緊耦合在了一起配并。想要消除這種緊密耦合的現(xiàn)象括荡,可以像下面這樣使用arguments.callee。
function factorial(num) {
if (num <= 1){
return 1;
} else {
return num * arguments.callee(num - 1);
}
}
使用arguments.callee到底有什么作用呢溉旋?請(qǐng)看下面的程序畸冲。
var trueFactorial = factorial;
factorial = funciton() { return 0; };
console.log(trueFactorial(5)); // 120
console.log(factorial(5)); // 0
將factorial函數(shù)的指針賦值給trueFactorial 后,即使原函數(shù)factorial被重新定義观腊,函數(shù)trueFactorial依然可以實(shí)現(xiàn)階乘的功能邑闲。
this,是函數(shù)內(nèi)部的另一個(gè)特殊對(duì)象梧油。this引用的是函數(shù)據(jù)以執(zhí)行的環(huán)境對(duì)象苫耸。說到this,頓時(shí)感覺有說不完的話要表達(dá)了儡陨。
思考鲸阔!在程序執(zhí)行的過程中,this到底指向(代表)了什么迄委?
請(qǐng)看下面的程序褐筛,程序有些長(zhǎng),也可以先看下面this指向的規(guī)律后再分析程序叙身。
// 1
var color = "red";
var obj = {
color: "blue"
};
function getColor() {
console.log(this.color);
};
getColor(); // "red"
obj.getColor = getColor;
obj.getColor(); // "blue"
// 2
function colorFull() {
var color = "yellow";
function getColor() {
console.log(this.color);
}
getColor(); // "red"
}
colorFull();
// 3
var name = "window";
var object = {
name: "object",
sayName: function() {
function innerFunction() {
console.log(this.name); // "window"
}
innerFunction();
return function() {
console.log(this.name); // "window"
}
}
}
object.sayName()();
// 4
var obj = {
value: 37,
fun: function() {
return this.value;
}
}
var fun1 = obj.fun;
console.log(fun1()); // undefined
// 原型鏈中的this
var obj = {
a: 2,
b: 5,
fun: function() {
return this.a + this.b;
}
}
var o = Object.create(obj);
o.a = 1;
o.b = 2;
console.log(o.fun()); // 3
// 構(gòu)造函數(shù)中的this
function C() {
this.a = 1;
}
var o1 = new C();
console.log(o1.a); // 1
function C2() {
this.a = 2;
return {a: 3}
}
var o2 = new C2();
console.log(o2.a); // 3 好詭異
從上面的程序中我們可以得出使用this的幾條規(guī)律渔扎。
1. this的指向是在函數(shù)執(zhí)行時(shí)候確定的。
2. 函數(shù)如果有直接調(diào)用者對(duì)象(如object.fun())信轿,那么函數(shù)內(nèi)部的this執(zhí)行該調(diào)用者對(duì)象(即object)晃痴;如果函數(shù)沒有直接調(diào)用者對(duì)象,那么函數(shù)內(nèi)部的this對(duì)象指向的是window對(duì)象财忽。原型鏈中的方法的this仍然指向調(diào)用它的對(duì)象
3. 構(gòu)造函數(shù)的this與被創(chuàng)建的新對(duì)象綁定倘核,當(dāng)構(gòu)造器返回的默認(rèn)值是一個(gè)this引用的對(duì)象是,可以手動(dòng)設(shè)置返回其他的對(duì)象即彪,如果返回值不是一個(gè)對(duì)象紧唱,則返回this。
上面是在es5中this的使用,在es6中新增了箭頭函數(shù)漏益。箭頭函數(shù)中的this指向與上述有所不同蛹锰。請(qǐng)看下面的程序。
var name = "window";
var object = {
name: "objcet",
sayName: function() {
return () => {
console.log(this.name); // "object"
}
}
}
object.sayName()();
var anotherObject = {
name: "anotherObject"
}
/*
object.sayName()執(zhí)行完后返回的箭頭函數(shù)里面的this已經(jīng)指向了object绰疤,此處的this已經(jīng)固定不會(huì)被更改了
*/
object.sayName().call(anotherObject); // "object"
/*
object.sayName只是一個(gè)函數(shù)指針铜犬,該函數(shù)并未執(zhí)行,其內(nèi)部嵌套的箭頭函數(shù)中的this
*/
object.sayName.call(anotherObject)(); // "anotherObject"
上面的es6中的箭頭函數(shù)轉(zhuǎn)換成es5轻庆,其實(shí)是下面這樣的癣猾。
var name = "window";
var object = {
name: "object",
sayName: function() {
var _this = this;
return function() {
console.log(_this.name);
}
}
}
箭頭函數(shù)不綁定this,它會(huì)捕捉其所在(即定義的位置)上下文的this值作為自己的this值
var adder = {
base: 1,
add: function(a) {
var f = v => v + this.base; // 此時(shí)已經(jīng)確定了this的值即adder
return f(a);
}
addThruCall: funcntion(a) {
var f = v => v + this.base;
var b = {
base: 2
}
return f.call(b, a);
}
};
console.log(adder.add(1)); // 2
console.log(adder.addThruCall(1)) // 仍然 2余爆,其內(nèi)部的this并沒有因?yàn)閏all() 而改變纷宇,其this值仍然為函數(shù)inFun的this值,指向?qū)ο骯dder
call(), apply(), bind()方法對(duì)于箭頭函數(shù)來說只是傳入?yún)?shù)龙屉,并不影響this的值呐粘。