javascript 函數和作用域(函數,this)(六)


重點。

一、函數

1、函數介紹

函數是一塊JavaScript代碼,被定義一次,但可執行和調用多次。JS中的函數也是對象,所以JS函數可以像其他對象那樣操作和傳遞,所以我們也常叫JS中的函數為函數對象。

注意:

返回Return

return語句可以使函數提前返回。

一個函數總會返回一個值,函數的返回值,依賴於return語句。

一般的函數調用:如果沒有return語句的話,默認會在所有代碼執行完以后返回undefined

如果是作為構造器,外部使用new去調用的話,如果沒有return語句,或者return的是基本類型的話,默認會將this作為返回。

反之,如果return的是對象,將這個對象作為new構造器的返回值。

2、函數的屬性和方法【20170501】

函數屬性

  • length屬性
  • prototype屬性
  • 自定義屬性

函數方法

  • call()
  • apply()

3、函數的相關內容

函數內容較多,重點有:

  • 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。

注意:在《jquery按鈕綁定特殊事件》

中第一次點擊按鈕觸發一個事件,第二次點擊觸發另一個事件。遇到類似的情況,

//不要這樣做

if(condition){
    function sayHi(){
        alert("Hi!");
    }
}else{
    function sayHi(){
        alert("Yo!");
    }
}

//可以這樣做
var sayHi;
if(condition){
    sayHi=function(){
        alert("Hi!");
    }
}else{
    sayHi=function(){
        alert("Yo!");
    }
}

4、命名函數表達式(NFE)

函數名字可以被調試器和開發工具識別。

 經典bug

 

命名函數表達式里的名字nfe在 函數對象創建所在的作用域中 正常情況下是訪問不到的。所以會報錯:nfe is not defined

老的IE瀏覽器(IE6~8)仍然可以訪問得到,但是nfe和func又不是同一個對象。

命名函數表達式相關規范:

規范規定:命名函數表達式名字(標識符)在函數體的作用域內有效,且不能被覆蓋。

b = function c() {
    a = 1, b = 2, c = 3;
    console.log(typeof c); // function
};

命名函數表達式應用

調試

 

 遞歸調用自己

//遞歸調用
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。

注意:語言設計上的不足【update 20170306】

調用函數時,this被綁定到全局對象。如下內部函數調用時this也是綁定到全局對象window。

function outer(){
    console.log(this===window);
    function inner(){
        console.log("內部函數:"+(this===window));
    }
    inner();
}
View Code

倘若語言設計正確,當內部函數被調用時,this應該仍然綁定到外部函數的this變量。這個設計錯誤的后果就是方法不能利用內部函數來幫助它工作,因為內部函數的this被綁定了錯誤的值,所以不能共享該方法對對象的訪問權。

一個簡單的解決方案如下:方法定義一個一個變量that並給它賦值為this,那么內部函數就可以通過that訪問到this。

<script>
//函數字面量創建add
var add=function(a,b){
    return a+b;
}
var myObject = {
    value: 0,
    increment: function(inc) {
        this.value += typeof inc === 'number' ? inc : 1;
    }
}

myObject.increment();
document.writeln(myObject.value); //1

myObject.increment(2);
document.writeln(myObject.value); //3

//給myObject增加一個double方法
myObject.double=function(){
    var that=this;//解決方法

    var helper=function(){
        that.value=add(that.value,that.value);
    }
    helper();  //以函數的形式調用helper
}

//以方法的形式調用double
myObject.double();
document.writeln(myObject.value); //6
</script>

 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

在方法調用模式中this到對象的綁定發生在調用時,這個“超級”延遲綁定(very late binding)使得函數可以對this高度重用。【update20170306】

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【重點】

如果在一個函數前面帶上new來調用,那么背地里將會創建一個連接到該函數的prototype成員的新對象,同時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*/

應用【bind的經典例子】

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拿到形參的個數。在函數內和函數外都有效。foo.length===arguments.callee.length【20170501】

arguments.length拿到實際傳參的個數。

arguments.callee當前正在執行的函數

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"

好處:使得編寫一個無須指定參數個數的函數成為可能。

注意設計錯誤

因為語言設計錯誤,arguments並不是一個真正的數組。它只是一個"類似數組(array-like)"的對象。arguments擁有一個length屬性,但arguments沒有任何數組的方法

五、bind和函數柯里化

 函數柯里化就是把一個函數拆成多個單元。

 1、柯里化

function add(a,b,c)    {
    console.log(a+'|'+b+'|'+c);
    console.log(a+b+c);
}

//不需要改變this,所以傳入一個undefined就可以了
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固定賦值給b參數。

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

 

本文作者starof,因知識本身在變化,作者也在不斷學習成長,文章內容也不定時更新,為避免誤導讀者,方便追根溯源,請諸位轉載注明出處:http://www.cnblogs.com/starof/p/6396450.html有問題歡迎與我討論,共同進步。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM