js系列教程4-函式、函式引數全解
全棧工程師開發手冊 (作者:欒鵬)
在js中,函式本身屬於物件的一種,因此可以定義、賦值,作為物件的屬性或者成為其他函式的引數。函式名只是函式這個物件類的引用。
函式定義
一、3種函式定義方式
【1】函式宣告語句
使用function關鍵字,後跟一組引數以及函式體
function funcname([arg1 [,arg2 [...,argn]]]){
statement;
}
【2】函式定義表示式
以表示式方式定義的函式,函式的名稱是可選的
var functionName = function([arg1 [,arg2 [...,argn]]]) {
statement;
}
var functionName = function funcName([arg1 [,arg2 [...,argn]]]){
statement;
}
匿名函式(anonymous function)也叫拉姆達函式,是function關鍵字後面沒有識別符號的函式
通常而言,以表示式方式定義函式時都不需要名稱,這會讓定義它們的程式碼更加緊湊。函式定義表示式特別適合用來定義那些只會使用一次的函式
var tensquared = (function(x) {return x*x;}(10)); //定義同時進行呼叫
而一個函式定義表示式包含名稱,函式的區域性作用域將會包含一個繫結到函式物件的名稱。實際上,函式的名稱將成為函式內部的一個區域性變數
var test = function fn(){
return fn;
}
console.log(test);//fn(){return fn;}
console.log(test());//fn(){return fn;}
console.log(test()());//fn(){return fn;}
個人理解,對於具名的函式表示式來說,函式名稱相當於函式物件的形參,只能在函式內部使用;而變數名稱相當於函式物件的實參,在函式內部和函式外部都可以使用
var test = function fn(){
return fn === test;
}
console.log(test());//true
console.log(test === fn);//ReferenceError: fn is not defined
函式定義了一個非標準的name屬性,通過這個屬性可以訪問到給定函式指定的名字,這個屬性的值永遠等於跟在function關鍵字後面的識別符號,匿名函式的name屬性為空
//IE11-瀏覽器無效,均輸出undefined
//chrome在處理匿名函式的name屬性時有問題,會顯示函式表示式的名字
function fn(){};
console.log(fn.name);//'fn'
var fn = function(){};
console.log(fn.name);//'',在chrome瀏覽器中會顯示'fn'
var fn = function abc(){};
console.log(fn.name);//'abc'
【3】Function建構函式
Function建構函式接收任意數量的引數,但最後一個引數始終都被看成是函式體,而前面的引數則枚舉出了新函式的引數
var functionName = new Function(['arg1' [,'arg2' [...,'argn']]],'statement;');
[注意]Function建構函式無法指定函式名稱,它建立的是一個匿名函式。
從技術上講,這是一個函式表示式。但不推薦使用,因為這種語法會導致解析兩次程式碼。第一次是解析常規javascript程式碼,第二次解析傳入建構函式中的字串,影響效能。
var sum = new Function('num1','num2','return num1 + num2');
//等價於
var sum = function(num1,num2){
return num1+num2;
}
Function()建構函式建立的函式,其函式體的編譯總是會在全域性作用域中執行。於是,Function()建構函式類似於在全域性作用域中執行的eval()
var test = 0;
function fn(){
var test = 1;
return new Function('return test');
}
console.log(fn()());//0
[注意]並不是所有的函式都可以成為建構函式
var o = new Math.min();//Uncaught TypeError: Math.min is not a constructor
二、函式宣告順序
函式宣告,相對於變數會優先載入。所以不用擔心函式宣告在呼叫前還是呼叫後。
呼叫函式時會先在本機活動物件中查詢,即當前js檔案中查詢,如果沒有才會向上查詢,所以若在兩個js檔案中定義相同函式名,這兩個js檔案內部呼叫各自的函式,其他js檔案中呼叫最後宣告的函式。
三、重複
變數的重複宣告是無用的,不會覆蓋之前同一作用域宣告的變數,但函式的重複宣告會覆蓋前面的宣告的同名函式或同名變數。
//變數的重複宣告無用
var a = 1;
var a;
console.log(a);//1
//覆蓋同名變數
var a;
function a(){
console.log(1);
}
a();//1
//覆蓋同名函式
a();//2
function a(){
console.log(1);
}
function a(){
console.log(2);
}
四、刪除
函式宣告語句建立的變數無法刪除,這一點和變數宣告一樣。
function foo(){
console.log(1);
}
delete foo;//false
console.log(foo());//1
函式返回值
所有函式都有返回值,沒有return語句時,預設返回內容為undefined,和其他面向物件的程式語言一樣,return語句不會阻止finally子句的執行。
function testFinnally(){
try{
return 2;
}catch(error){
return 1;
}finally{
return 0;
}
}
testFinnally();//0
如果函式呼叫時在前面加上了new字首,且返回值不是一個物件,則返回this(該新物件)。
function fn(){
this.a = 2;
return 1;
}
var test = new fn();
console.log(test);//{a:2}
console.log(test.constructor);//fn(){this.a = 2;return 1;}
如果返回值是一個物件,則返回該物件。
function fn(){
this.a = 2;
return {a:1};
}
var test = new fn();
console.log(test);//{a:1}
console.log(test.constructor);//Object() { [native code] }
函式呼叫
javascript一共有4種呼叫模式:函式呼叫模式、方法呼叫模式、構造器呼叫模式和間接呼叫模式。
【1】函式呼叫模式
當一個函式並非一個物件的屬性時,那麼它就是被當做一個函式來呼叫的。對於普通的函式呼叫來說,函式的返回值就是呼叫表示式的值。
function add(x,y){
return x+y;
}
var sum = add(3,4);
console.log(sum)//7
使用函式呼叫模式呼叫函式時,非嚴格模式下,this被繫結到全域性物件;在嚴格模式下,this是undefined
function add(x,y){
console.log(this);//window
}
add();
function add(x,y){
'use strict';
console.log(this);//undefined
}
add();//window
因此,’this’可以用來判斷當前是否是嚴格模式
var strict = (function(){return !this;}());
重寫
因為函式呼叫模式的函式中的this繫結到全域性物件,所以會發生全域性屬性被重寫的現象
var a = 0;
function fn(){
this.a = 1;
}
fn();
console.log(this,this.a,a);//window 1 1
【2】方法呼叫模式
當一個函式被儲存為物件的一個屬性時,我們稱它為一個方法。當一個方法被呼叫時,this被繫結到該物件。如果呼叫表示式包含一個提取屬性的動作,那麼它就是被當做一個方法來呼叫。
var o = {
m: function(){
console.log(1);
}
};
o.m();//1
方法可以使用this訪問自己所屬的物件,所以它能從物件中取值或對物件進行修改。this到物件的繫結發生在呼叫的時候。通過this可取得它們所屬物件的上下文的方法稱為公共方法。
var o = {
a: 1,
m: function(){
return this;
},
n: function(){
this.a = 2;
}
};
console.log(o.m().a);//1
o.n();
console.log(o.m().a);//2
任何函式只要作為方法呼叫實際上都會傳入一個隱式的實參——這個實參是一個物件,方法呼叫的母體就是這個物件,通常來講,基於那個物件的方法可以執行多種操作,方法呼叫的語法已經很清晰地表明瞭函式將基於一個物件進行操作
rect.setSize(width,height);
setRectSize(rect,width,height);
假設上面兩行程式碼的功能完全一樣,它們都作用於一個假定的物件rect。可以看出,第一行的方法呼叫語法非常清晰地表明這個函式執行的載體是rect物件,函式中的所有操作都將基於這個物件
和變數不同,關鍵字this沒有作用域的限制,巢狀的函式不會從呼叫它的函式中繼承this。如果巢狀函式作為方法呼叫,其this的值指向呼叫它的物件。如果巢狀函式作為函式呼叫,其this值不是全域性物件(非嚴格模式下)就是undefined(嚴格模式下)
var o = {
m: function(){
function n(){
return this;
}
return n();
}
}
console.log(o.m());//window
var o = {
m: function(){
function n(){
'use strict';
return this;
}
return n();
}
}
console.log(o.m());//undefined
如果想訪問這個外部函式的this值,需要將this的值儲存在一個變數裡,這個變數和內部函式都同在一個作用域內。通常使用變數self或that來儲存this
var o = {
m: function(){
var self = this;
console.log(this === o);//true
function n(){
console.log(this === o);//false
console.log(self === o);//true
return self;
}
return n();
}
}
console.log(o.m() === o);//true
【3】建構函式呼叫模式
如果函式或者方法呼叫之前帶有關鍵字new,它就構成建構函式呼叫
function fn(){
this.a = 1;
};
var obj = new fn();
console.log(obj.a);//1
如果建構函式呼叫在圓括號內包含一組實參列表,先計算這些實參表示式,然後傳入函式內
function fn(x){
this.a = x;
};
var obj = new fn(2);
console.log(obj.a);//2
如果建構函式沒有形參,javascript建構函式呼叫的語法是允許省略實參列表和圓括號的。凡是沒有形參的建構函式呼叫都可以省略圓括號
var o = new Object();
//等價於
var o = new Object;
[注意]儘管建構函式看起來像一個方法呼叫,它依然會使用這個新物件作為呼叫上下文。也就是說,在表示式new o.m()中,呼叫上下文並不是o
var o = {
m: function(){
return this;
}
}
var obj = new o.m();
console.log(obj,obj === o);//{} false
console.log(obj.constructor === o.m);//true
建構函式通常不使用return關鍵字,它們通常初始化新物件,當建構函式的函式體執行完畢時,它會顯式返回。在這種情況下,建構函式呼叫表示式的計算結果就是這個新物件的值
function fn(){
this.a = 2;
}
var test = new fn();
console.log(test);//{a:2}
如果建構函式使用return語句但沒有指定返回值,或者返回一個原始值,那麼這時將忽略返回值,同時使用這個新物件作為呼叫結果
function fn(){
this.a = 2;
return;
}
var test = new fn();
console.log(test);//{a:2}
如果建構函式顯式地使用return語句返回一個物件,那麼呼叫表示式的值就是這個物件
var obj = {a:1};
function fn(){
this.a = 2;
return obj;
}
var test = new fn();
console.log(test);//{a:1}
【4】間接呼叫模式
javascript中函式也是物件,函式物件也可以包含方法。call()和apply()方法可以用來間接地呼叫函式。
這兩個方法都允許顯式指定呼叫所需的this值,也就是說,任何函式可以作為任何物件的方法來呼叫,哪怕這個函式不是那個物件的方法。兩個方法都可以指定呼叫的實參。call()方法使用它自有的實參列表作為函式的實參,apply()方法則要求以陣列的形式傳入引數。
var obj = {};
function sum(x,y){
return x+y;
}
console.log(sum.call(obj,1,2));//3
console.log(sum.apply(obj,[1,2]));//3
函式引數
arguments
javascript中的函式定義並未指定函式形參的型別,函式呼叫也未對傳入的實參值做任何型別檢查。實際上,javascript函式呼叫甚至不檢查傳入形參的個數。
function add(x){
return x+1;
}
console.log(add(1));//2
console.log(add('1'));//'11'
console.log(add());//NaN
console.log(add(1,2));//2
同名形參
在非嚴格模式下,函式中可以出現同名形參,且只能訪問最後出現的該名稱的形參。
function add(x,x,x){
return x;
}
console.log(add(1,2,3));//3
而在嚴格模式下,出現同名形參會丟擲語法錯誤
function add(x,x,x){
'use strict';
return x;
}
console.log(add(1,2,3));//SyntaxError: Duplicate parameter name not allowed in this context
引數個數
當實參比函式宣告指定的形參個數要少,剩下的形參都將設定為undefined值
function add(x,y){
console.log(x,y);//1 undefined
}
add(1);
常常使用邏輯或運算子給省略的引數設定一個合理的預設值
function add(x,y){
y = y || 2;
console.log(x,y);//1 2
}
add(1);
[注意]實際上,使用y || 2是不嚴謹的,顯式地設定假值(undefined、null、false、0、-0、”、NaN)也會得到相同的結果。所以應該根據實際場景進行合理設定
當實參比形參個數要多時,剩下的實參沒有辦法直接獲得,需要使用即將提到的arguments物件
javascript中的引數在內部用一個數組表示。函式接收到的始終都是這個陣列,而不關心陣列中包含哪些引數。在函式體內可以通過arguments物件來訪問這個引數陣列,從而獲取傳遞給函式的每一個引數。arguments物件並不是Array的例項,它是一個類陣列物件,可以使用方括號語法訪問它的每一個元素
function add(x){
console.log(arguments[0],arguments[1],arguments[2])//1 2 3
return x+1;
}
add(1,2,3);
arguments物件的length屬性顯示實參的個數,函式的length屬性顯示形參的個數
function add(x,y){
console.log(arguments.length)//3
return x+1;
}
add(1,2,3);
console.log(add.length);//2
形參只是提供便利,但不是必需的
function add(){
return arguments[0] + arguments[1];
}
console.log(add(1,2));//3
物件引數
當一個函式包含超過3個形參時,要記住呼叫函式中實參的正確順序實在讓人頭疼
function arraycopy(/*array*/from,/*index*/form_start,/*array*/to,/*index*/to_start,/*integer*/length){
//todo
}
通過名/值對的形式來傳入引數,這樣引數的順序就無關緊要了。定義函式的時候,傳入的實參都寫入一個單獨的物件之中,在呼叫的時候傳入一個物件,物件中的名/值對是真正需要的實引數據
function easycopy(args){
arraycopy(args.from,args.from_start || 0,args.to,args.to_start || 0, args.length);
}
var a = [1,2,3,4],b =[];
easycopy({from:a,to:b,length:4});
以函式為引數
函式本身是一個物件,因此可以將函式作為另一個函式的引數,進而實現函式回撥,功能等同於c++中的函式指標。
function printf(str){
dom1.innerText += str.toString()+"\n"; //設定dom1顯示的文字。變數也可以自動呼叫其他js檔案中的dom1變數。dom1會先在當前檔案中查詢,然後向之前引用的js檔案查詢,再向之後引用的js檔案查詢
}
function callfunction(myfunction,myargument){ //函式作為其他函式的引數
return myfunction(myargument); //呼叫回撥函式
}
callfunction(printf,"hello world");
同步
當形參與實參的個數相同時,arguments物件的值和對應形參的值保持同步
function test(num1,num2){
console.log(num1,arguments[0]);//1 1
arguments[0] = 2;
console.log(num1,arguments[0]);//2 2
num1 = 10;
console.log(num1,arguments[0]);//10 10
}
test(1);
[注意]雖然命名引數和對應arguments物件的值相同,但並不是相同的名稱空間。它們的名稱空間是獨立的,但值是同步的
但在嚴格模式下,arguments物件的值和形參的值是獨立的
function test(num1,num2){
'use strict';
console.log(num1,arguments[0]);//1 1
arguments[0] = 2;
console.log(num1,arguments[0]);//1 2
num1 = 10;
console.log(num1,arguments[0]);//10 2
}
test(1);
當形參並沒有對應的實參時,arguments物件的值與形參的值並不對應
function test(num1,num2){
console.log(num1,arguments[0]);//undefined,undefined
num1 = 10;
arguments[0] = 5;
console.log(num1,arguments[0]);//10,5
}
test();
內部屬性
【callee】
arguments物件有一個名為callee的屬性,該屬性是一個指標,指向擁有這個arguments物件的函式
下面是經典的階乘函式
function factorial(num){
if(num <=1){
return 1;
}else{
return num* factorial(num-1);
}
}
console.log(factorial(5));//120
但是,上面這個函式的執行與函式名緊緊耦合在了一起,可以使用arguments.callee可以消除函式解耦
function factorial(num){
if(num <=1){
return 1;
}else{
return num* arguments.callee(num-1);
}
}
console.log(factorial(5));//120
但在嚴格模式下,訪問這個屬性會丟擲TypeError錯誤
function factorial(num){
'use strict';
if(num <=1){
return 1;
}else{
return num* arguments.callee(num-1);
}
}
//TypeError: 'caller', 'callee', and 'arguments' properties may not be accessed on strict mode functions or the arguments objects for calls to them
console.log(factorial(5));
這時,可以使用具名的函式表示式
var factorial = function fn(num){
if(num <=1){
return 1;
}else{
return num*fn(num-1);
}
};
console.log(factorial(5));//120
【caller】
實際上有兩個caller屬性
【1】函式的caller
函式的caller屬性儲存著呼叫當前函式的函式的引用,如果是在全域性作用域中呼叫當前函式,它的值是null
function outer(){
inner();
}
function inner(){
console.log(inner.caller);//outer(){inner();}
}
outer();
function inner(){
console.log(inner.caller);//null
}
inner();
在嚴格模式下,訪問這個屬性會丟擲TypeError錯誤
function inner(){
'use strict';
//TypeError: 'caller' and 'arguments' are restricted function properties and cannot be accessed in this context
console.log(inner.caller);
}
inner();
【2】arguments物件的caller
該屬性始終是undefined,定義這個屬性是為了分清arguments.caller和函式的caller屬性
function inner(x){
console.log(arguments.caller);//undefined
}
inner(1);
同樣地,在嚴格模式下,訪問這個屬性會丟擲TypeError錯誤
function inner(x){
'use strict';
//TypeError: 'caller' and 'arguments' are restricted function properties and cannot be accessed in this context
console.log(arguments.caller);
}
inner(1);
函式過載
javascript函式不能像傳統意義上那樣實現過載。而在其他語言中,可以為一個函式編寫兩個定義,只要這兩個定義的簽名(接受的引數的型別和數量)不同即可
javascript函式沒有簽名,因為其引數是由包含0或多個值的陣列來表示的。而沒有函式簽名,真正的過載是不可能做到的
//後面的宣告覆蓋了前面的宣告
function addSomeNumber(num){
return num + 100;
}
function addSomeNumber(num){
return num + 200;
}
var result = addSomeNumber(100);//300
只能通過檢查傳入函式中引數的型別和數量並作出不同的反應,來模仿方法的過載
function doAdd(){
if(arguments.length == 1){
alert(arguments[0] + 10);
}else if(arguments.length == 2){
alert(arguments[0] + arguments[1]);
}
}
doAdd(10);//20
doAdd(30,20);//50
引數傳遞
javascript中所有函式的引數都是按值傳遞的。也就是說,把函式外部的值複製到函式內部的引數,就和把值從一個變數複製到另一個變數一樣
【1】基本型別值
在向引數傳遞基本型別的值時,被傳遞的值會被複制給一個區域性變數(命名引數或arguments物件的一個元素)
function addTen(num){
num += 10;
return num;
}
var count = 20;
var result = addTen(count);
console.log(count);//20,沒有變化
console.log(result);//30
【2】引用型別值
在向引數傳遞引用型別的值時,會把這個值在記憶體中的地址複製給一個區域性變數,因此這個區域性變數的變化會反映在函式的外部
function setName(obj){
obj.name = 'test';
}
var person = new Object();
setName(person);
console.log(person.name);//'test'
當在函式內部重寫引用型別的形參時,這個變數引用的就是一個區域性物件了。而這個區域性物件會在函式執行完畢後立即被銷燬
function setName(obj){
obj.name = 'test';
console.log(person.name);//'test'
obj = new Object();
obj.name = 'white';
console.log(person.name);//'test'
}
var person = new Object();
setName(person);
函式屬性
【length屬性】
arguments物件的length屬性表示實參個數,而函式的length屬性則表示形參個數
function add(x,y){
console.log(arguments.length)//3
console.log(add.length);//2
}
add(1,2,3);
【name屬性】
函式定義了一個非標準的name屬性,通過這個屬性可以訪問到給定函式指定的名字,這個屬性的值永遠等於跟在function關鍵字後面的識別符號,匿名函式的name屬性為空
//IE11-瀏覽器無效,均輸出undefined
//chrome在處理匿名函式的name屬性時有問題,會顯示函式表示式的名字
function fn(){};
console.log(fn.name);//'fn'
var fn = function(){};
console.log(fn.name);//'',在chrome瀏覽器中會顯示'fn'
var fn = function abc(){};
console.log(fn.name);//'abc'
[注意]name屬性早就被瀏覽器廣泛支援,但是直到ES6才將其寫入了標準
ES6對這個屬性的行為做出了一些修改。如果將一個匿名函式賦值給一個變數,ES5的name屬性,會返回空字串,而ES6的name屬性會返回實際的函式名
var func1 = function () {};
func1.name //ES5: ""
func1.name //ES6: "func1"
如果將一個具名函式賦值給一個變數,則ES5和ES6的name屬性都返回這個具名函式原本的名字
var bar = function baz() {};
bar.name //ES5: "baz"
bar.name //ES6: "baz"
Function建構函式返回的函式例項,name屬性的值為“anonymous”
(new Function).name // "anonymous"
bind返回的函式,name屬性值會加上“bound ”字首
function foo() {};
foo.bind({}).name // "bound foo"
(function(){}).bind({}).name // "bound "
【prototype屬性】
每一個函式都有一個prototype屬性,這個屬性指向一個物件的引用,這個物件稱做原型物件(prototype object)。每一個函式都包含不同的原型物件。將函式用做建構函式時,新建立的物件會從原型物件上繼承屬性
function fn(){};
var obj = new fn;
fn.prototype.a = 1;
console.log(obj.a);//1
函式方法
【apply()和call()】
每個函式都包含兩個非繼承而來的方法:apply()和call()。這兩個方法的用途都是在特定的作用域中呼叫函式,實際上等於函式體內this物件的值
要想以物件o的方法來呼叫函式f(),可以這樣使用call()和apply()
f.call(o);
f.apply(o);
假設o中不存在m方法,則等價於:
o.m = f; //將f儲存為o的臨時方法
o.m(); //呼叫它,不傳入引數
delete o.m; //將臨時方法刪除
下面是一個實際的例子
window.color = "red";
var o = {color: "blue"};
function sayColor(){
console.log(this.color);
}
sayColor(); //red
sayColor.call(this); //red
sayColor.call(window); //red
sayColor.call(o); //blue
//sayColor.call(o)等價於:
o.sayColor = sayColor;
o.sayColor(); //blue
delete o.sayColor;
apply()方法接收兩個引數:一個是在其中執行函式的作用域(或者可以說成是要呼叫函式的母物件,它是呼叫上下文,在函式體內通過this來獲得對它的引用),另一個是引數陣列。其中,第二個引數可以是Array的例項,也可以是arguments物件
function sum(num1, num2){
return num1 + num2;
}
//因為執行函式的作用域是全域性作用域,所以this代表的是window物件
function callSum1(num1, num2){
return sum.apply(this, arguments);
}
function callSum2(num1, num2){
return sum.apply(this, [num1, num2]);
}
console.log(callSum1(10,10));//20
console.log(callSum2(10,10));//20
call()方法與apply()方法的作用相同,它們的區別僅僅在於接收引數的方式不同。對於call()方法而言,第一個引數是this值沒有變化,變化的是其餘引數都直接傳遞給函式。換句話說,在使用call()方法時,傳遞給函式的引數必須逐個列舉出來
function sum(num1, num2){
return num1 + num2;
}
function callSum(num1, num2){
return sum.call(this, num1, num2);
}
console.log(callSum(10,10)); //20
至於是使用apply()還是call(),完全取決於採取哪種函式傳遞引數的方式最方便。如果打算直接傳入arguments物件,或者包含函式中先接收到的也是一個數組,那麼使用apply()肯定更方便;否則,選擇call()可能更合適
在非嚴格模式下,使用函式的call()或apply()方法時,null或undefined值會被轉換為全域性物件。而在嚴格模式下,函式的this值始終是指定的值
var color = 'red';
function displayColor(){
console.log(this.color);
}
displayColor.call(null);//red
var color = 'red';
function displayColor(){
'use strict';
console.log(this.color);
}
displayColor.call(null);//TypeError: Cannot read property 'color' of null
應用
【1】呼叫物件的原生方法
var obj = {};
obj.hasOwnProperty('toString');// false
obj.hasOwnProperty = function (){
return true;
};
obj.hasOwnProperty('toString');// true
Object.prototype.hasOwnProperty.call(obj, 'toString');// false
【2】找出陣列最大元素
javascript不提供找出陣列最大元素的函式。結合使用apply方法和Math.max方法,就可以返回陣列的最大元素
var a = [10, 2, 4, 15, 9];
Math.max.apply(null, a);//15
【3】將類陣列物件轉換成真正的陣列
Array.prototype.slice.apply({0:1,length:1});//[1]
或者
[].prototype.slice.apply({0:1,length:1});//[1]
【4】將一個數組的值push到另一個數組中
var a = [];
Array.prototype.push.apply(a,[1,2,3]);
console.log(a);//[1,2,3]
Array.prototype.push.apply(a,[2,3,4]);
console.log(a);//[1,2,3,2,3,4]
【5】繫結回撥函式的物件
由於apply方法(或者call方法)不僅繫結函式執行時所在的物件,還會立即執行函式,因此不得不把繫結語句寫在一個函式體內。更簡潔的寫法是採用下面介紹的bind方法
var o = {};
o.f =