1. 程式人生 > >javascript 函式和作用域(函式,this)(六)

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的模擬實現。