javascript 函式和作用域(函式,this)(六)
重點。
一、函式
函式是一塊JavaScript程式碼,被定義一次,但可執行和呼叫多次。JS中的函式也是物件,所以JS函式可以像其他物件那樣操作和傳遞,所以我們也常叫JS中的函式為函式物件。
注意:
函式的返回值,依賴於return語句。
一般的函式呼叫:如果沒有return語句的話,預設會在所有程式碼執行完以後返回undefined。
如果是作為構造器,外部使用new去呼叫的話,如果沒有return語句,或者return的是基本型別的話,預設會將this作為返回。
反之,如果return的是物件,將這個物件作為new構造器的返回值。
函式內容較多,重點有:
- this
- arguments
- 作用域
- 不同調用方式
- 直接呼叫foo()
- 物件方法o.method()
- 構造器new Foo()
- call/apply/bind呼叫 func.call(o)
- 不同建立方法
二、函式宣告和表示式
1、函式宣告
function add(a,b){ a=+a; b=+b; if(isNaN(a)||isNaN(b)){ return; } return a+b; }
一個完整的語句以function開頭,也不加括號,也不加歎號,也不會把它括起來,也不會把它作為賦值語句的右值等待。這樣定義的函式就叫函式宣告。
2、函式表示式
1、 函式變數
函式表示式賦值給變數。
//函式變數 function variable var add=function(a,b){ //do sth }
2、立即執行函式表示式(IEF)
把一個匿名函式用括號括起來,再去直接呼叫。
//立即執行函式表示式 IEF(Immediately Executed Function) (function(){ })();
3、作為返回值的函式表示式
將函式物件作為返回值,函式也是物件。
//first-class function return function(){ //do sth }
4、命名式函式表示式(不常用)
同樣是賦值給一個變數,但這個函式不是匿名函式,而是有一個名字的函式,
//NFE (Named Function Expression) var add=function(a,b){ //do sth }
3、函式宣告和函式表示式的區別
最主要的區別是函式宣告會被前置。
函式宣告,在宣告前呼叫也可以,因為會前置。
函式表示式在宣告前呼叫會報錯:undefined is not a function。
函式表示式中
var add=function(a,b){//do sth};
變數的宣告會被提前,var add會提前,add被提前後它的值是undefined。
當把一個undefined的變數嘗試像函式那樣去呼叫的時候,就會報異常:undefined is not a function。
4、命名函式表示式(NFE)
經典bug
命名函式表示式裡的名字nfe在 函式物件建立所在的作用域中 正常情況下是訪問不到的。所以會報錯:nfe is not defined
老的IE瀏覽器(IE6~8)仍然可以訪問得到,但是nfe和func又不是同一個物件。
命名函式表示式應用:
除錯
遞迴呼叫自己
//遞迴呼叫 var func=function nfe(){/** do sth. **/ nfe();}
5、函式構造器
除了函式宣告和函式表示式,還有一種不常見的建構函式的方式—使用函式構造器。
Function()中引數可以有多個,前面的引數表示函式物件裡面的形參,最後一個引數表示函式體裡面的程式碼。
函式體裡的程式碼也是字串,這也是為什麼函式構造器不常用的原因。
var func=new Function('a','b','console.log(a+b);'); //建立一個物件,有a,b2個形參,函式體裡面是輸出a+b func(1,2);//3 //不管用new還是不用new最後得到的結果都是一樣的。 var func=Function('a','b','console.log(a+b);'); func(1,2);//3
Function構造器作用域和其他處理與一般函式宣告和函式表示式的差異
1、在Function構造器裡面建立的變數仍然是區域性變數,
//CASE1 Function('var localVal="local";console.log(localVal);')();//立即執行 console.log(typeof localVal); //undefined
2、Function函式宣告能訪問到全域性變數,卻訪問不到它的外函式中定義的變數。
local不可訪問,全域性global可以訪問。
三、this
1、全域性的this(瀏覽器)
全域性作用域下的this一般指向全域性物件,瀏覽器彙總的全域性物件就是window。
2、一般函式的this(瀏覽器)
全域性作用域下直接呼叫f1(),this就仍然指向全域性物件,瀏覽器中就是window,在node.js裡面就是global物件。
嚴格模式下直接呼叫f2(),this執行是undefined。
3、作為物件方法的 函式的this
只要將函式作為物件的方法o.f,this就會指向這個物件o。
var o={ prop:37, f:function(){ return this.prop; } } console.log(o.f()); //37
或者
var o={prop:37}; function independent(){ return this.prop; } o.f=independent; console.log(o.f()); //37
4、物件原型鏈上的this
物件o有個屬性f。
p是個空物件,並且p的原型指向o。給p新增2個屬性a和b,再呼叫p.f()。
呼叫p.f()的時候呼叫的是p的原型o上面的這樣一個屬性f。所以物件原型鏈上的this呼叫時指向的還是物件。
var o={f:function(){return this.a+this.b}}; var p=Object.create(o); p.a=1; p.b=4; console.log(p.f()); //5
5、get/set方法與this
get set方法中的this一般也是指向get,set方法所在的物件。
function modulus(){ return Math.sqrt(this.re*this.re+this.im*this.im); } var o={ re:1, im:-1, get phase(){ return Math.atan2(this.im,this.re); } } Object.defineProperty(o,'modulus',{ get:modulus, enumerable:true, configurable:true }) console.log(o.phase,o.modulus); //-0.7853981633974483 1.4142135623730951
6、構造器中的this
將MyClass作為了構造器來用。
function MyClass(){ this.a=37; } var o=new MyClass(); /*this指向空物件,並且這個空物件的原型指向MyClass.prototype, this作為返回值,因為沒有return 所以物件o就會有屬性a為37*/ console.log(o.a);//37
注意:
return語句返回的是物件的話,將該物件作為返回值,所以下面a就是38。
function C2(){ this.a=37; return {a:38}; } o=new C2(); console.log(o.a);//38
7、call/apply方法與this
function add(c,d){ console.log(this.a+this.b+c+d); } var o={a:1,b:3}; //call呼叫 add.call(o,5,7);//16 //1+3+5+7=16 //apply呼叫 add.apply(o,[10,20]);//34 //1+3+10+20=34
應用
function bar(){ console.log(Object.prototype.toString.call(this)); } bar.call(7); //[object Number]
call和apply如果this傳入null或者undefined的話,this會指向全域性物件,在瀏覽器裡就是window。
如果是嚴格模式的話:
傳入this為null和undefined,那this就是null和undefined。
8、bind與this[ES5提供,IE9+才有]
想把某一個物件作為this的時候,就傳進去。
function f(){ return this.a; } var g=f.bind({a:"test"}); console.log(g());//test /* 繫結一次,多次呼叫,仍然實現這樣一個繫結,比apply和call更高效 */ var o={a:37,f:f,g:g}; /*f屬性賦值為直接的f方法,g賦值為剛才繫結之後的方法*/ console.log(o.f(),o.g()); //37 "test" /*o.f()通過物件的屬性的方式呼叫的,返回37*/ /*比較特殊的一點,使用bind方法綁定了之後,即使把新繫結之後的方法作為物件的屬性去呼叫,仍然會按照之前的繫結去走,所以仍然返回test*/
應用
this.x=9; //相當於window.x=9 var module={ x:81, getX:function(){return this.x;} }; module.getX();//81 作為物件方法呼叫 var getX=module.getX();//把物件的方法賦值給一個變數 getX();//9 this指向window,呼叫的是window的x var boundGetx=getX.bind(module); boundGetx();//81 通過bind修改執行時的this
四、函式屬性arguments
foo.length拿到形參的個數。
arguments.length拿到實際傳參的個數。
foo.name拿到函式名。
坑:嘗試通過arguments[2]=100修改未傳入的z的值,z還是undefined。
就是說:引數如果沒傳進來的話,arguments和引數沒有改下修改這樣的繫結關係。
function foo(x,y,z){ console.log(arguments.length); //2 console.log(arguments[0]); //1 arguments[0]=10; console.log(x); //有繫結關係,形參x被修改為10 arguments[2]=100;//z未傳入 console.log(z);//沒有繫結關係,z仍然是undefined console.log(arguments.callee===foo);//true,嚴格模式禁止使用 } foo(1,2); console.log(foo.length);//3 console.log(foo.name);//"foo"
五、bind和函式柯里化
函式柯里化就是把一個函式拆成多個單元。
1、柯里化
function add(a,b,c) { console.log(a+'|'+b+'|'+c); console.log(a+b+c); } var func=add.bind(undefined,100); func(1,2); //100|1|2 //103 var func2=func.bind(undefined,200); func2(10); //100|200|10 //310
100固定賦值給a引數。
再柯里化一次,200固定賦值給a引數。
2、實際例子
/*getConfig獲取一套配置 在不同的頁面中配置可能是不一樣的, 但是在同一個模組下,可能前2個引數,或者某些引數是一樣的 */ function getConfig(colors,size,otherOptions){ console.log(colors,size,otherOptions); } /*this無所謂,寫個null或者undefined都可以,可能在某個模組下color都是#CC0000,size都是1024*768*/ var defaultConfig=getConfig.bind(null,"#CC0000","1024*768"); /*拿到defaultConfig這樣一個模組級別的通用配置以後,只要傳入最後一個引數,可能是每個頁面下的單獨配置*/ defaultConfig("123"); //#CC0000 1024*768 123 defaultConfig("456"); //#CC0000 1024*768 456
六、bind和new
用new去呼叫,在this這個層面上.bind()的作用會被忽略。
用new的時候,即使綁定了bind,也會被忽略。
func()直接呼叫,this會指向bind引數{a:1},return this.a就會返回1.
執行了this.b=100其實是給{a:1}加了個b屬性,最後是{a: 1, b: 100}只是不會作為返回值,因為指定了返回值。
new的話,return除非是物件,不是物件的話會把this作為返回值,並且this會被初始化為預設的一個空物件,這個物件的原型是foo.prototye。
所以這裡new func()呼叫的時候,即使我們指定了bind方法,this仍然會指向沒有bind時所指向的空物件,空物件的原型指向foo.prototype,這個空物件的b屬性被設定為100,整個物件會作為一個返回值返回,會忽略return this.a。所以用new func()呼叫後會返回物件字面量{b:100}。
七、bind方法模擬
在老的瀏覽器裡怎樣實現bind方法?模擬實現。
bind方法實現2個功能,繫結this和柯里化。
MDN的模擬實現。